Initial commit with JWK parsing implementation.
This commit is contained in:
commit
0c53843e9b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.*.sw?
|
||||
src/test/*/runner
|
46
README.md
Normal file
46
README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Nim JWT
|
||||
|
||||
This is a standards-compliant implementation of the JWT, JWK, JWS, and JWE
|
||||
standards in Nim, using the [BearSSL](https://www.bearssl.org/) library to
|
||||
provide the underlying cryptographic primatives.
|
||||
|
||||
## Supported Algorithms (JWA)
|
||||
|
||||
[RFC7518 (JWA)][rfc7518] registers
|
||||
cryptographic algorithms and identifiers to be used with the JSON Web Signature
|
||||
(JWS), JSON Web Encryption (JWE), and JSON Web Key (JWK) specifications.
|
||||
|
||||
This library supports all REQUIRED algorithms. Specifically, the following
|
||||
algorithms are supported:
|
||||
|
||||
### Digital Signature and MACs (JWS)
|
||||
|
||||
From [RFC7518 section 3][rfc7518-s3] the following algorithms are supported:
|
||||
|
||||
* *TBD*
|
||||
|
||||
### Key Management
|
||||
|
||||
From [RFC7518 section 4][rfc7518-s4] the following algorithms are supported:
|
||||
|
||||
* *TBD*
|
||||
|
||||
### Content Encryption
|
||||
|
||||
From [RFC7518 section 5][rfc7518-s5] the following algorithms are supported:
|
||||
|
||||
* *TBD*
|
||||
|
||||
### Keys
|
||||
|
||||
From [RFC7518 section 6][rfc7518-s6] the following key types are supported:
|
||||
|
||||
* EC
|
||||
* RSA
|
||||
* otc
|
||||
|
||||
[rfc7518]: https://datatracker.ietf.org/doc/html/rfc7518
|
||||
[rfc7518-s3]: https://datatracker.ietf.org/doc/html/rfc7518#section-3
|
||||
[rfc7518-s4]: https://datatracker.ietf.org/doc/html/rfc7518#section-4
|
||||
[rfc7518-s5]: https://datatracker.ietf.org/doc/html/rfc7518#section-5
|
||||
[rfc7518-s6]: https://datatracker.ietf.org/doc/html/rfc7518#section-6
|
16
jwt.nimble
Normal file
16
jwt.nimble
Normal file
@ -0,0 +1,16 @@
|
||||
# Package
|
||||
|
||||
version = "0.1.0"
|
||||
author = "Jonathan Bernard"
|
||||
description = "Full JWT, JWS, JWE, and JWK implementation for Nim, compliant with RFCs 7515-7519."
|
||||
license = "GPL-3.0-or-later"
|
||||
srcDir = "src/main"
|
||||
skipDirs = @["private"]
|
||||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 1.4.8"
|
||||
requires "bearssl"
|
||||
|
||||
task unittest, "Run the unit test suites.":
|
||||
exec "nim c -r src/test/unit/runner"
|
283
rfc/rfc7514.txt
Normal file
283
rfc/rfc7514.txt
Normal file
@ -0,0 +1,283 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Independent Submission M. Luckie
|
||||
Request for Comments: 7514 CAIDA
|
||||
Category: Experimental 1 April 2015
|
||||
ISSN: 2070-1721
|
||||
|
||||
|
||||
Really Explicit Congestion Notification (RECN)
|
||||
|
||||
Abstract
|
||||
|
||||
This document proposes a new ICMP message that a router or host may
|
||||
use to advise a host to reduce the rate at which it sends, in cases
|
||||
where the host ignores other signals provided by packet loss and
|
||||
Explicit Congestion Notification (ECN).
|
||||
|
||||
Status of This Memo
|
||||
|
||||
This document is not an Internet Standards Track specification; it is
|
||||
published for examination, experimental implementation, and
|
||||
evaluation.
|
||||
|
||||
This document defines an Experimental Protocol for the Internet
|
||||
community. This is a contribution to the RFC Series, independently
|
||||
of any other RFC stream. The RFC Editor has chosen to publish this
|
||||
document at its discretion and makes no statement about its value for
|
||||
implementation or deployment. Documents approved for publication by
|
||||
the RFC Editor are not a candidate for any level of Internet
|
||||
Standard; see Section 2 of RFC 5741.
|
||||
|
||||
Information about the current status of this document, any errata,
|
||||
and how to provide feedback on it may be obtained at
|
||||
http://www.rfc-editor.org/info/rfc7514.
|
||||
|
||||
Copyright Notice
|
||||
|
||||
Copyright (c) 2015 IETF Trust and the persons identified as the
|
||||
document authors. All rights reserved.
|
||||
|
||||
This document is subject to BCP 78 and the IETF Trust's Legal
|
||||
Provisions Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info) in effect on the date of
|
||||
publication of this document. Please review these documents
|
||||
carefully, as they describe your rights and restrictions with respect
|
||||
to this document.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Luckie Experimental [Page 1]
|
||||
|
||||
RFC 7514 RECN 1 April 2015
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||
1.1. Requirements Language . . . . . . . . . . . . . . . . . . 2
|
||||
2. RECN Message Format . . . . . . . . . . . . . . . . . . . . . 2
|
||||
2.1. Advice to Implementers . . . . . . . . . . . . . . . . . 3
|
||||
2.2. Relationship to ICMP Source Quench . . . . . . . . . . . 4
|
||||
3. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 4
|
||||
4. Security Considerations . . . . . . . . . . . . . . . . . . . 4
|
||||
5. Normative References . . . . . . . . . . . . . . . . . . . . 4
|
||||
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 5
|
||||
|
||||
1. Introduction
|
||||
|
||||
The deployment of Explicit Congestion Notification (ECN) [RFC3168]
|
||||
remains stalled. While most operating systems support ECN, it is
|
||||
currently disabled by default because of fears that enabling ECN will
|
||||
break transport protocols. This document proposes a new ICMP message
|
||||
that a router or host may use to advise a host to reduce the rate at
|
||||
which it sends, in cases where the host ignores other signals such as
|
||||
packet loss and ECN. We call this message the "Really Explicit
|
||||
Congestion Notification" (RECN) message because it delivers a less
|
||||
subtle indication of congestion than packet loss and ECN.
|
||||
|
||||
1.1. Requirements Language
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in RFC 2119 [RFC2119].
|
||||
|
||||
2. RECN Message Format
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Code | Checksum |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Explicit Notification |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| As much of the invoking packet as possible |
|
||||
+ without the ICMP packet exceeding 576 bytes |
|
||||
| in IPv4 or the minimum MTU in IPv6 |
|
||||
|
||||
Type
|
||||
|
||||
IPv4: 4
|
||||
|
||||
IPv6: 201
|
||||
|
||||
|
||||
|
||||
Luckie Experimental [Page 2]
|
||||
|
||||
RFC 7514 RECN 1 April 2015
|
||||
|
||||
|
||||
Code
|
||||
|
||||
0
|
||||
|
||||
Checksum
|
||||
|
||||
The checksum is the 16-bit ones's complement of the one's
|
||||
complement sum of the ICMP message starting with the ICMP type
|
||||
field. When an RECN message is encapsulated in an IPv6 packet,
|
||||
the computation includes a "pseudo-header" of IPv6 header fields
|
||||
as specified in Section 8.1 of [RFC2460]. For computing the
|
||||
checksum, the checksum field is first set to zero.
|
||||
|
||||
Explicit Notification
|
||||
|
||||
A 4-byte value that conveys an explicit notification in the ASCII
|
||||
format defined in [RFC20]. This field MUST NOT be set to zero.
|
||||
|
||||
Description
|
||||
|
||||
An RECN message SHOULD be sent by a router in response to a host
|
||||
that is generating traffic at a rate persistently unfair to other
|
||||
competing flows and that has not reacted to previous packet losses
|
||||
or ECN marks.
|
||||
|
||||
The contents of an RECN message MUST be conveyed to the user
|
||||
responsible for the traffic. Precisely how this is accomplished
|
||||
will depend on the capabilities of the host. If text-to-speech
|
||||
capabilities are available, the contents should be converted to
|
||||
sound form and audibly rendered. If the system is currently
|
||||
muted, a pop-up message will suffice.
|
||||
|
||||
2.1. Advice to Implementers
|
||||
|
||||
As the Explicit Notification field is only 4 bytes, it is not
|
||||
required that the word be null terminated. A client implementation
|
||||
should be careful not to use more than those 4 bytes. If a router
|
||||
chooses a word less than 4 bytes in size, it should null-terminate
|
||||
that word.
|
||||
|
||||
A router should not necessarily send an RECN message every time it
|
||||
discards a packet due to congestion. Rather, a router should send
|
||||
these messages whenever it discards a burst of packets from a single
|
||||
sender. For every packet a router discards in a single burst, it
|
||||
should send an RECN message. A router may form short sentences
|
||||
composed of different 4-byte words, and a host should play these
|
||||
sentences back to the user. A router may escalate the content in the
|
||||
Explicit Notification field if it determines that a sender has not
|
||||
|
||||
|
||||
|
||||
Luckie Experimental [Page 3]
|
||||
|
||||
RFC 7514 RECN 1 April 2015
|
||||
|
||||
|
||||
adjusted its transmission rate in response to previous RECN messages.
|
||||
There is no upper bound on the intensity of the escalation, either in
|
||||
content or sentence length.
|
||||
|
||||
2.2. Relationship to ICMP Source Quench
|
||||
|
||||
The RECN message was inspired by the ICMP Source Quench message,
|
||||
which is now deprecated [RFC6633]. Because the RECN message uses a
|
||||
similar approach, an RECN message uses the same ICMP type when
|
||||
encapsulated in IPv4 as was used by the ICMP Source Quench message.
|
||||
|
||||
3. IANA Considerations
|
||||
|
||||
This is an Experimental RFC; the experiment will conclude two years
|
||||
after the publication of this RFC. During the experiment,
|
||||
implementers are free to use words of their own choosing (up to four
|
||||
letters) in RECN messages. If RECN becomes a Standard of any kind, a
|
||||
list of allowed words will be maintained in an IANA registry. There
|
||||
are no IANA actions required at this time.
|
||||
|
||||
4. Security Considerations
|
||||
|
||||
ICMP messages may be used in various attacks [RFC5927]. An attacker
|
||||
may use the RECN message to cause a host to reduce their transmission
|
||||
rate for no reason. To prevent such an attack, a host must ensure
|
||||
the quoted message corresponds to an active flow on the system, and
|
||||
an attacker MUST set the security flag defined in [RFC3514] to 1 when
|
||||
the RECN message is carried in an IPv4 packet.
|
||||
|
||||
5. Normative References
|
||||
|
||||
[RFC20] Cerf, V., "ASCII format for network interchange", STD 80,
|
||||
RFC 20, October 1969,
|
||||
<http://www.rfc-editor.org/info/rfc20>.
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997,
|
||||
<http://www.rfc-editor.org/info/rfc2119>.
|
||||
|
||||
[RFC2460] Deering, S. and R. Hinden, "Internet Protocol, Version 6
|
||||
(IPv6) Specification", RFC 2460, December 1998,
|
||||
<http://www.rfc-editor.org/info/rfc2460>.
|
||||
|
||||
[RFC3168] Ramakrishnan, K., Floyd, S., and D. Black, "The Addition
|
||||
of Explicit Congestion Notification (ECN) to IP", RFC
|
||||
3168, September 2001,
|
||||
<http://www.rfc-editor.org/info/rfc3168>.
|
||||
|
||||
|
||||
|
||||
|
||||
Luckie Experimental [Page 4]
|
||||
|
||||
RFC 7514 RECN 1 April 2015
|
||||
|
||||
|
||||
[RFC3514] Bellovin, S., "The Security Flag in the IPv4 Header", RFC
|
||||
3514, April 2003,
|
||||
<http://www.rfc-editor.org/info/rfc3514>.
|
||||
|
||||
[RFC5927] Gont, F., "ICMP Attacks against TCP", RFC 5927, July 2010,
|
||||
<http://www.rfc-editor.org/info/rfc5927>.
|
||||
|
||||
[RFC6633] Gont, F., "Deprecation of ICMP Source Quench Messages",
|
||||
RFC 6633, May 2012,
|
||||
<http://www.rfc-editor.org/info/rfc6633>.
|
||||
|
||||
Author's Address
|
||||
|
||||
Matthew Luckie
|
||||
CAIDA
|
||||
University of California, San Diego
|
||||
9500 Gilman Drive
|
||||
La Jolla, CA 92093-0505
|
||||
United States
|
||||
|
||||
EMail: mjl@caida.org
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Luckie Experimental [Page 5]
|
||||
|
3307
rfc/rfc7515.txt
Normal file
3307
rfc/rfc7515.txt
Normal file
File diff suppressed because it is too large
Load Diff
2859
rfc/rfc7516.txt
Normal file
2859
rfc/rfc7516.txt
Normal file
File diff suppressed because it is too large
Load Diff
2243
rfc/rfc7517.txt
Normal file
2243
rfc/rfc7517.txt
Normal file
File diff suppressed because it is too large
Load Diff
3867
rfc/rfc7518.txt
Normal file
3867
rfc/rfc7518.txt
Normal file
File diff suppressed because it is too large
Load Diff
1683
rfc/rfc7519.txt
Normal file
1683
rfc/rfc7519.txt
Normal file
File diff suppressed because it is too large
Load Diff
6723
rfc/rfc7520.txt
Normal file
6723
rfc/rfc7520.txt
Normal file
File diff suppressed because it is too large
Load Diff
3
src/main/jwt.nim
Normal file
3
src/main/jwt.nim
Normal file
@ -0,0 +1,3 @@
|
||||
import jwt/jwk
|
||||
|
||||
export jwk
|
191
src/main/jwt/jwk.nim
Normal file
191
src/main/jwt/jwk.nim
Normal file
@ -0,0 +1,191 @@
|
||||
import std/json, std/options, std/sequtils
|
||||
|
||||
import ../private/jsonutils
|
||||
|
||||
type
|
||||
JwkKeyType* = enum EcPublic, EcPrivate, RsaPublic, RsaPrivate, Octet
|
||||
JwkKty* = enum ktyEC = "EC", ktyRSA = "RSA", ktyOctet = "oct"
|
||||
|
||||
JwkUse* = enum jwkuSignature = "sig", jwkuEncrypt = "enc"
|
||||
|
||||
JwkOps* = enum jwkopSign = "sign", jwkopVerify = "verify",
|
||||
jwkopEncrypt = "encrypt", jwkopDecrypt = "decrypt",
|
||||
jwkopWrapKey = "wrapKey", jwkopUnwrapKey = "unwrapKey",
|
||||
jwkopDeriveKey = "deriveKey", jwkopDeriveBits = "deriveBits"
|
||||
|
||||
JwkAlg* = enum
|
||||
HS256 = "HS256", HS384 = "HS384", HS512 = "HS512",
|
||||
RS256 = "RS256", RS384 = "RS384", RS512 = "RS512",
|
||||
ES256 = "ES256", ES384 = "ES384", ES512 = "ES512",
|
||||
PS256 = "PS256", PS384 = "PS384", PS512 = "PS512",
|
||||
|
||||
algNone = "none", algDir = "dir",
|
||||
|
||||
RSA1_5 = "RSA1_5", RSA_OAEP = "RSA-OAEP", RSA_OAEP_256 = "RSA-OEAP-256",
|
||||
|
||||
A128KW = "A128KW", A192KW = "A192KW", A256KW = "A256KW",
|
||||
|
||||
ECDH_ES = "ECDH_ES", ECDH_ES_A128KW = "ECDH_ES_A128KW",
|
||||
ECDH_ES_A192KW = "ECDH_ES_A192KW", ECDH_ES_A256KW = "ECDH_ES_A256KW",
|
||||
|
||||
A128GCMKW = "A128GCMKW", A192GCMKW = "A192GCMKW", A256GCMKW = "A256GCMKW",
|
||||
|
||||
PBES2_HS256_A128KW = "PBES2_HS256_A128KW",
|
||||
PBES2_HS384_A192KW = "PBES2_HS384_A192KW",
|
||||
PBES2_HS512_A256KW = "PBES2_HS512_A256KW",
|
||||
|
||||
A128CBC_HS256 = "A128CBC_HS256",
|
||||
A192CBC_HS384 = "A192CBC_HS384",
|
||||
A256CBC_HS512 = "A256CBC_HS512",
|
||||
|
||||
A128GCM = "A128GCM", A192GCM = "A192GCM", A256GCM = "A256GCM"
|
||||
|
||||
EcCrv* = enum P256 = "P-256", P384 = "P-384", P521 = "P-521"
|
||||
|
||||
EcPubKey = object of RootObj
|
||||
crv*: string
|
||||
x*: string
|
||||
y*: Option[string]
|
||||
|
||||
EcPrvKey* = object of EcPubKey
|
||||
d*: string
|
||||
|
||||
RsaPubKey* = object of RootObj
|
||||
n*: string
|
||||
e*: string
|
||||
|
||||
RsaOtherPrime* = object
|
||||
r*: string
|
||||
d*: string
|
||||
t*: string
|
||||
|
||||
RsaPrvKey* = object of RsaPubKey
|
||||
d*: string
|
||||
p*: Option[string]
|
||||
q*: Option[string]
|
||||
dp*: Option[string]
|
||||
dq*: Option[string]
|
||||
qi*: Option[string]
|
||||
oth*: Option[seq[RsaOtherPrime]]
|
||||
|
||||
OctetKey* = object
|
||||
k*: string
|
||||
|
||||
JWK* = object
|
||||
json: JsonNode
|
||||
kty*: string
|
||||
use*: Option[string]
|
||||
key_ops*: Option[string]
|
||||
alg*: Option[string]
|
||||
kid*: Option[string]
|
||||
x5u*: Option[string]
|
||||
x5c*: Option[string]
|
||||
x5t*: Option[string]
|
||||
x5tS256*: Option[string]
|
||||
|
||||
case keyKind*: JwkKeyType
|
||||
of EcPublic: ecPub*: EcPubKey
|
||||
of EcPrivate: ecPrv*: EcPrvKey
|
||||
of RsaPublic: rsaPub*: RsaPubKey
|
||||
of RsaPrivate: rsaPrv*: RsaPrvKey
|
||||
of Octet: octKey*: OctetKey
|
||||
|
||||
JwkSet* = seq[JWK]
|
||||
|
||||
func `[]`*(jwk: JWK, key: string): JsonNode = jwk.json[key]
|
||||
func `[]=`*(jwk: JWK, key: string, val: JsonNode): void = jwk.json[key] = val
|
||||
|
||||
func parseEcPubKey*(n: JsonNode): EcPubKey =
|
||||
EcPubKey(
|
||||
crv: n.reqStrVal("crv"),
|
||||
x: n.reqStrVal("x"),
|
||||
y: n.optStrVal("y"))
|
||||
|
||||
func parseEcPrvKey*(n: JsonNode): EcPrvKey =
|
||||
let pk = parseEcPubKey(n)
|
||||
EcPrvKey(crv: pk.crv, x: pk.x, y: pk.y, d: n.reqStrVal("d"))
|
||||
|
||||
func parseRsaPubKey*(n: JsonNode): RsaPubKey =
|
||||
RsaPubKey(
|
||||
n: n.reqStrVal("n"),
|
||||
e: n.reqStrVal("e"))
|
||||
|
||||
func parseRsaOtherPrime(n: JsonNode): RsaOtherPrime =
|
||||
RsaOtherPrime(
|
||||
r: n.reqStrVal("r"),
|
||||
d: n.reqStrVal("d"),
|
||||
t: n.reqStrVal("t"))
|
||||
|
||||
func parseRsaPrvKey*(n: JsonNode): RsaPrvKey =
|
||||
let pk = parseRsaPubKey(n)
|
||||
RsaPrvKey(n: pk.n, e: pk.e,
|
||||
d: n.reqStrVal("d"),
|
||||
p: n.optStrVal("p"),
|
||||
q: n.optStrVal("q"),
|
||||
dp: n.optStrVal("dp"),
|
||||
dq: n.optStrVal("dq"),
|
||||
qi: n.optStrVal("qi"),
|
||||
oth:
|
||||
if n.hasKey("oth"):
|
||||
some(n["oth"].getElems.mapIt(parseRsaOtherPrime(it)))
|
||||
else: none[seq[RsaOtherPrime]]()
|
||||
)
|
||||
|
||||
|
||||
func parseOctetKey*(n: JsonNode): OctetKey =
|
||||
OctetKey(k: n.reqStrVal("k"))
|
||||
|
||||
func parseJwk*(n: JsonNode): JWK =
|
||||
# TODO: documentation, example, handle encrypted keys (JWEs)
|
||||
case n["kty"].getStr(""):
|
||||
|
||||
of $ktyEC:
|
||||
if n.hasKey("d"):
|
||||
result = JWK(
|
||||
kty: $ktyEC,
|
||||
keyKind: EcPrivate,
|
||||
ecPrv: parseEcPrvKey(n))
|
||||
else:
|
||||
result = JWK(
|
||||
kty: $ktyEC,
|
||||
keyKind: EcPublic,
|
||||
ecPub: parseEcPubKey(n))
|
||||
|
||||
of $ktyRSA:
|
||||
if n.hasKey("d"):
|
||||
result = JWK(
|
||||
kty: $ktyRSA,
|
||||
keyKind: RsaPrivate,
|
||||
rsaPrv: parseRsaPrvKey(n))
|
||||
else:
|
||||
result = JWK(
|
||||
kty: $ktyRSA,
|
||||
keyKind: RsaPublic,
|
||||
rsaPub: parseRsaPubKey(n))
|
||||
|
||||
of $ktyOctet:
|
||||
result = JWK(
|
||||
kty: $ktyOctet,
|
||||
keyKind: Octet,
|
||||
octKey: parseOctetKey(n))
|
||||
|
||||
else: raise newException(ValueError,
|
||||
"Unrecognized or missing kty: '" & n["kty"].getStr("") & "'")
|
||||
|
||||
result.json = n
|
||||
result.use = n.optStrVal("use")
|
||||
result.key_ops = n.optStrVal("key_ops")
|
||||
result.alg = n.optStrVal("alg")
|
||||
result.kid = n.optStrVal("kid")
|
||||
result.x5u = n.optStrVal("x5u")
|
||||
result.x5c = n.optStrVal("x5c")
|
||||
result.x5t = n.optStrVal("x5t")
|
||||
result.x5tS256 = n.optStrVal("x5t#S256")
|
||||
|
||||
func parseJwkSet*(n: JsonNode): JwkSet =
|
||||
# TODO: documentation, examples, handled encrypted set (JWE)
|
||||
if not n.hasKey("keys") or n["keys"].kind != JArray:
|
||||
raise newException(ValueError, "JWK Set is missing the 'keys' member, " &
|
||||
"or 'keys' is not an array.")
|
||||
|
||||
return n["keys"].getElems.mapIt(parseJwk(it))
|
12
src/main/private/jsonutils.nim
Normal file
12
src/main/private/jsonutils.nim
Normal file
@ -0,0 +1,12 @@
|
||||
import std/json, std/options, std/strutils
|
||||
|
||||
func reqStrVal*(n: JsonNode, key: string): string =
|
||||
if n.hasKey(key):
|
||||
let val = n[key].getStr("")
|
||||
if not val.isEmptyOrWhitespace: return val
|
||||
|
||||
raise newException(ValueError, "missing value for '" & key & "'")
|
||||
|
||||
func optStrVal*(n: JsonNode, key: string): Option[string] =
|
||||
if n.hasKey(key): some(n[key].getStr)
|
||||
else: none[string]()
|
2
src/test/unit/config.nims
Normal file
2
src/test/unit/config.nims
Normal file
@ -0,0 +1,2 @@
|
||||
switch("path", "../../main")
|
||||
switch("verbosity", "0")
|
3
src/test/unit/runner.nim
Normal file
3
src/test/unit/runner.nim
Normal file
@ -0,0 +1,3 @@
|
||||
import std/unittest
|
||||
|
||||
import ./tjwk
|
217
src/test/unit/tjwk.nim
Normal file
217
src/test/unit/tjwk.nim
Normal file
@ -0,0 +1,217 @@
|
||||
import std/json, std/options, std/unittest
|
||||
|
||||
import jwt/jwk
|
||||
|
||||
suite "jwt/jwk":
|
||||
|
||||
const rfc7517A1ExamplePubKeysStr = """
|
||||
{"keys":
|
||||
[
|
||||
{"kty":"EC",
|
||||
"crv":"P-256",
|
||||
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
||||
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
||||
"use":"enc",
|
||||
"kid":"1"},
|
||||
{"kty":"RSA",
|
||||
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
|
||||
"e":"AQAB",
|
||||
"alg":"RS256",
|
||||
"kid":"2011-04-29"}
|
||||
]
|
||||
}"""
|
||||
|
||||
let rfc7517A1ExamplePubKeysJson = parseJson(rfc7517A1ExamplePubKeysStr)
|
||||
|
||||
const rfc7517A2ExamplePrvKeysStr = """
|
||||
{"keys":
|
||||
[
|
||||
{"kty":"EC",
|
||||
"crv":"P-256",
|
||||
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
||||
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
||||
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
|
||||
"use":"enc",
|
||||
"kid":"1"},
|
||||
|
||||
{"kty":"RSA",
|
||||
"n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
|
||||
"e":"AQAB",
|
||||
"d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",
|
||||
"p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",
|
||||
"q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",
|
||||
"dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",
|
||||
"dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",
|
||||
"qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",
|
||||
"alg":"RS256",
|
||||
"kid":"2011-04-29"}
|
||||
]
|
||||
}"""
|
||||
|
||||
let rfc7517A2ExamplePrvKeysJson = parseJson(rfc7517A2ExamplePrvKeysStr)
|
||||
|
||||
test "parseEcPubKey parses valid keys":
|
||||
let keyNode = rfc7517A1ExamplePubKeysJson["keys"][0]
|
||||
let jwk = parseJwk(keyNode)
|
||||
|
||||
check:
|
||||
jwk.kty == $ktyEC
|
||||
jwk.keyKind == EcPublic
|
||||
jwk.use.isSome
|
||||
jwk.use.get == $jwkuEncrypt
|
||||
jwk.kid.isSome
|
||||
jwk.kid.get == "1"
|
||||
jwk.key_ops.isNone
|
||||
jwk.ecPub.crv == $P256
|
||||
jwk.ecPub.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
|
||||
jwk.ecPub.y.isSome
|
||||
jwk.ecPub.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
|
||||
|
||||
test "parseEcPubKey rejects keys missing required values":
|
||||
let keyNode = rfc7517A1ExamplePubKeysJson["keys"][0]
|
||||
|
||||
let withoutCrv = parseJson($keyNode)
|
||||
withoutCrv.delete("crv")
|
||||
expect ValueError: discard parseJwk(withoutCrv)
|
||||
|
||||
let withoutX = parseJson($keyNode)
|
||||
withoutX.delete("x")
|
||||
expect ValueError: discard parseJwk(withoutX)
|
||||
|
||||
let withoutY = parseJson($keyNode)
|
||||
withoutY.delete("y")
|
||||
let jwkWithoutY = parseJwk(withoutY)
|
||||
|
||||
check:
|
||||
jwkWithoutY.kty == $ktyEc
|
||||
jwkWithoutY.keyKind == EcPublic
|
||||
jwkWithoutY.ecPub.y.isNone
|
||||
|
||||
test "parseRsaPubKey parses valid keys":
|
||||
let keyNode = rfc7517A1ExamplePubKeysJson["keys"][1]
|
||||
let jwk = parseJwk(keyNode)
|
||||
|
||||
check:
|
||||
jwk.kty == $ktyRSA
|
||||
jwk.keyKind == RsaPublic
|
||||
jwk.alg.isSome
|
||||
jwk.alg.get == $RS256
|
||||
jwk.use.isNone
|
||||
jwk.kid.isSome
|
||||
jwk.kid.get == "2011-04-29"
|
||||
jwk.key_ops.isNone
|
||||
jwk.rsaPub.e == "AQAB"
|
||||
jwk.rsaPub.n == "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
|
||||
|
||||
test "parseRsaPubKey rejects keys missing required values":
|
||||
let keyNode = rfc7517A1ExamplePubKeysJson["keys"][1]
|
||||
|
||||
let withoutN = parseJson($keyNode)
|
||||
withoutN.delete("n")
|
||||
expect ValueError: discard parseJwk(withoutN)
|
||||
|
||||
let withoutE = parseJson($keyNode)
|
||||
withoutE.delete("e")
|
||||
expect ValueError: discard parseJwk(withoutE)
|
||||
|
||||
test "parseEcPrvKey parses valid keys":
|
||||
let keyNode = rfc7517A2ExamplePrvKeysJson["keys"][0]
|
||||
let jwk = parseJwk(keyNode)
|
||||
|
||||
check:
|
||||
jwk.kty == $ktyEC
|
||||
jwk.keyKind == EcPrivate
|
||||
jwk.use.isSome
|
||||
jwk.use.get == $jwkuEncrypt
|
||||
jwk.kid.isSome
|
||||
jwk.kid.get == "1"
|
||||
jwk.key_ops.isNone
|
||||
jwk.ecPrv.crv == $P256
|
||||
jwk.ecPrv.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
|
||||
jwk.ecPrv.y.isSome
|
||||
jwk.ecPrv.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
|
||||
jwk.ecPrv.d == "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"
|
||||
|
||||
test "parseEcPrvKey rejects keys missing required values":
|
||||
let keyNode = rfc7517A2ExamplePrvKeysJson["keys"][0]
|
||||
|
||||
let withoutCrv = parseJson($keyNode)
|
||||
withoutCrv.delete("crv")
|
||||
expect ValueError: discard parseJwk(withoutCrv)
|
||||
|
||||
let withoutX = parseJson($keyNode)
|
||||
withoutX.delete("x")
|
||||
expect ValueError: discard parseJwk(withoutX)
|
||||
|
||||
let withoutY = parseJson($keyNode)
|
||||
withoutY.delete("y")
|
||||
let jwkWithoutY = parseJwk(withoutY)
|
||||
|
||||
check:
|
||||
jwkWithoutY.kty == $ktyEc
|
||||
jwkWithoutY.keyKind == EcPrivate
|
||||
jwkWithoutY.ecPrv.y.isNone
|
||||
|
||||
test "parseRsaPrvKey parses valid keys":
|
||||
let keyNode = rfc7517A2ExamplePrvKeysJson["keys"][1]
|
||||
let jwk = parseJwk(keyNode)
|
||||
|
||||
check:
|
||||
jwk.kty == $ktyRSA
|
||||
jwk.keyKind == RsaPrivate
|
||||
jwk.alg.isSome
|
||||
jwk.alg.get == $RS256
|
||||
jwk.use.isNone
|
||||
jwk.kid.isSome
|
||||
jwk.kid.get == "2011-04-29"
|
||||
jwk.key_ops.isNone
|
||||
jwk.rsaPrv.e == "AQAB"
|
||||
jwk.rsaPrv.n == "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
|
||||
jwk.rsaPrv.d == "X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q"
|
||||
jwk.rsaPrv.p.isSome
|
||||
jwk.rsaPrv.p.get == "83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs"
|
||||
jwk.rsaPrv.q.isSome
|
||||
jwk.rsaPrv.q.get == "3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk"
|
||||
jwk.rsaPrv.dp.isSome
|
||||
jwk.rsaPrv.dp.get == "G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0"
|
||||
jwk.rsaPrv.dq.isSome
|
||||
jwk.rsaPrv.dq.get == "s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk"
|
||||
jwk.rsaPrv.qi.isSome
|
||||
jwk.rsaPrv.qi.get == "GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU"
|
||||
jwk.rsaPrv.oth.isNone
|
||||
|
||||
test "parseRsaPrvKey rejects keys missing required values":
|
||||
let keyNode = rfc7517A2ExamplePrvKeysJson["keys"][1]
|
||||
|
||||
let withoutN = parseJson($keyNode)
|
||||
withoutN.delete("n")
|
||||
expect ValueError: discard parseJwk(withoutN)
|
||||
|
||||
let withoutE = parseJson($keyNode)
|
||||
withoutE.delete("e")
|
||||
expect ValueError: discard parseJwk(withoutE)
|
||||
|
||||
let withoutP = parseJson($keyNode)
|
||||
withoutP.delete("p")
|
||||
let jwkWithoutP = parseJwk(withoutP)
|
||||
check jwkWithoutP.rsaPrv.p.isNone
|
||||
|
||||
test "parseJwkSet (public key examples)":
|
||||
let jwkSet = parseJwkSet(rfc7517A1ExamplePubKeysJson)
|
||||
|
||||
check:
|
||||
jwkSet.len == 2
|
||||
jwkSet[0].kty == $ktyEC
|
||||
jwkSet[0].keyKind == EcPublic
|
||||
jwkSet[1].kty == $ktyRSA
|
||||
jwkSet[1].keyKind == RsaPublic
|
||||
|
||||
test "parseJwkSet (private key examples)":
|
||||
let jwkSet = parseJwkSet(rfc7517A2ExamplePrvKeysJson)
|
||||
|
||||
check:
|
||||
jwkSet.len == 2
|
||||
jwkSet[0].kty == $ktyEC
|
||||
jwkSet[0].keyKind == EcPrivate
|
||||
jwkSet[1].kty == $ktyRSA
|
||||
jwkSet[1].keyKind == RsaPrivate
|
Loading…
x
Reference in New Issue
Block a user