Initial commit with JWK parsing implementation.

This commit is contained in:
Jonathan Bernard 2021-11-22 15:33:59 -06:00
commit 0c53843e9b
16 changed files with 21457 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.*.sw?
src/test/*/runner

46
README.md Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

2859
rfc/rfc7516.txt Normal file

File diff suppressed because it is too large Load Diff

2243
rfc/rfc7517.txt Normal file

File diff suppressed because it is too large Load Diff

3867
rfc/rfc7518.txt Normal file

File diff suppressed because it is too large Load Diff

1683
rfc/rfc7519.txt Normal file

File diff suppressed because it is too large Load Diff

6723
rfc/rfc7520.txt Normal file

File diff suppressed because it is too large Load Diff

3
src/main/jwt.nim Normal file
View File

@ -0,0 +1,3 @@
import jwt/jwk
export jwk

191
src/main/jwt/jwk.nim Normal file
View 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))

View 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]()

View File

@ -0,0 +1,2 @@
switch("path", "../../main")
switch("verbosity", "0")

3
src/test/unit/runner.nim Normal file
View File

@ -0,0 +1,3 @@
import std/unittest
import ./tjwk

217
src/test/unit/tjwk.nim Normal file
View 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