Rename to jwt_full to avoid collision with existing nim-jwt.
This commit is contained in:
1 parent
35a9cf682c
commit
4cc2a4dee5
25 files changed
+30
-30
No files matched your search
@@ -0,0 +1,184 @@
|
||||
import std/tables
|
||||
import bearssl, bearssl_pkey_decoder
|
||||
|
||||
import ../../jwa
|
||||
import ../../jwk
|
||||
|
||||
import ../encoding
|
||||
|
||||
import ./hash
|
||||
import ./pem
|
||||
|
||||
type
|
||||
EcPublicKeyObj = object
|
||||
curve*: EcCurve
|
||||
q*: string
|
||||
bearKey: EcPublicKey
|
||||
|
||||
EcPrivateKeyObj = object
|
||||
curve*: EcCurve
|
||||
x*: string
|
||||
bearKey: EcPrivateKey
|
||||
|
||||
func toBearSslCurveConst(curve: EcCurve): int32 =
|
||||
result = case curve:
|
||||
of P256: EC_secp256r1
|
||||
of P384: EC_secp384r1
|
||||
of P521: EC_secp521r1
|
||||
|
||||
func fromBearSslCurveConst(curve: int32): EcCurve =
|
||||
result = case curve:
|
||||
of EC_secp256r1: P256
|
||||
of EC_secp384r1: P384
|
||||
of EC_secp521r1: P521
|
||||
else: P256
|
||||
|
||||
func initEcPublicKeyObj(curve: EcCurve, q: string): EcPublicKeyObj =
|
||||
result = EcPublicKeyObj(curve: curve, q: q)
|
||||
result.bearKey.curve = curve.toBearSslCurveConst
|
||||
result.bearKey.q = cast[ptr char](result.q.cstring)
|
||||
result.bearKey.qlen = q.len
|
||||
|
||||
func initEcPrivateKeyObj(curve: EcCurve, x: string): EcPrivateKeyObj =
|
||||
result = EcPrivateKeyObj(curve: curve, x: x)
|
||||
result.bearKey.curve = curve.toBearSslCurveConst
|
||||
result.bearKey.x = cast[ptr char](result.x.cstring)
|
||||
result.bearKey.xlen = x.len
|
||||
|
||||
proc toEcPublicKey(jwk: JWK): EcPublicKeyObj =
|
||||
## Convert an ECDSA public key in JWK format to the wrapper for BearSSL's
|
||||
## ECPublicKey struct.
|
||||
|
||||
# Confusingly, the JWK standard and BearSSL use contradictory names for EC
|
||||
# key components. In JWK parlance, x and y (optional) are the public key
|
||||
# components. BearSSL only requires one public point, and calls this q.
|
||||
|
||||
let keyObj = if jwk.keyKind == EcPublic: jwk.ecPub
|
||||
else: jwk.ecPrv
|
||||
|
||||
return initEcPublicKeyObj(
|
||||
curve = keyObj.crv,
|
||||
q = b64UrlDecode(keyObj.x))
|
||||
|
||||
proc toEcPublicKey(pem: string): EcPublicKeyObj =
|
||||
var pkCtx: PkeyDecoderContext
|
||||
pkeyDecoderInit(addr pkCtx)
|
||||
decodePem(pem, "PUBLIC KEY", addr pkCtx,
|
||||
cast[BearPemDecoderCallback](pkeyDecoderPush))
|
||||
|
||||
let k = pkCtx.key.ec
|
||||
return initEcPublicKeyObj(k.curve.fromBearSslCurveConst, $cstring(k.q))
|
||||
|
||||
proc toEcPrivateKey(jwk: JWK): EcPrivateKeyObj =
|
||||
## Convert an ECDSA private key in JWK format to the wrapper for BearSSL's
|
||||
## ECPrivateKey struct.
|
||||
|
||||
# Confusingly, the JWK standard and BearSSL use contradictory names for EC
|
||||
# key components. In JWK parlance, d is the private key components. BearSSL
|
||||
# calls this x.
|
||||
return initEcPrivateKeyObj(
|
||||
curve = jwk.ecPrv.crv,
|
||||
x = b64UrlDecode(jwk.ecPrv.d))
|
||||
|
||||
proc toEcPrivateKey(pem: string): EcPrivateKeyObj =
|
||||
var skCtx: SkeyDecoderContext
|
||||
skeyDecoderInit(addr skCtx)
|
||||
decodePem(pem, "EC PRIVATE KEY", addr skCtx,
|
||||
cast[BearPemDecoderCallback](skeyDecoderPush))
|
||||
|
||||
let k = skCtx.key.ec
|
||||
return initEcPrivateKeyObj(k.curve.fromBearSslCurveConst, $cstring(k.x))
|
||||
|
||||
proc getEcHashCfg(alg: JwtAlgorithm): HashCfg =
|
||||
let hashAlg = case alg:
|
||||
of ES256: SHA256
|
||||
of ES384: SHA384
|
||||
of ES512: SHA512
|
||||
else: raise newException(ValueError,
|
||||
"Unsupported ECDSA signature algorithm '" & $alg & "'")
|
||||
|
||||
result = HASHES[hashAlg]
|
||||
|
||||
proc bearEcSign(
|
||||
message: string,
|
||||
alg: JwtAlgorithm,
|
||||
key: EcPrivateKeyObj
|
||||
): string =
|
||||
|
||||
let hashCfg = getEcHashCfg(alg)
|
||||
let hashed = hash(message, hashcfg.alg)
|
||||
|
||||
let ecSignImpl = ecdsaSignRawGetDefault()
|
||||
|
||||
# The signature fucntion will return the length of the signature in bytes,
|
||||
# but we need to pre-allocate the space for it to write into. Per BearSSL
|
||||
# documentation, the maximum signature size will by 132 bytes (see
|
||||
# https://bearssl.org/apidoc/bearssl__ec_8h.html#ab7154b0c899ceb3af062c69016258667)
|
||||
result = newString(132)
|
||||
|
||||
let sigLen = ecSignImpl(
|
||||
addr ecAllM15,
|
||||
hashCfg.vtable,
|
||||
cast[ptr char](unsafeAddr hashed[0]),
|
||||
unsafeAddr key.bearKey,
|
||||
cast[ptr char](addr result[0]))
|
||||
|
||||
if sigLen == 0: raise newException(Exception, "EC signature failed")
|
||||
result.setLen(sigLen)
|
||||
|
||||
proc bearEcVerify(
|
||||
message, signature: string,
|
||||
alg: JwtAlgorithm,
|
||||
key: EcPublicKeyObj
|
||||
): bool =
|
||||
|
||||
let hashCfg = getEcHashCfg(alg)
|
||||
let hashed = hash(message, hashCfg.alg)
|
||||
|
||||
let ecVerifyImpl = ecdsaVrfyRawGetDefault()
|
||||
let resultCode = ecVerifyImpl(
|
||||
addr ecAllM15,
|
||||
cast[ptr char](unsafeAddr hashed[0]),
|
||||
hashed.len,
|
||||
unsafeAddr key.bearKey,
|
||||
cast[ptr char](unsafeAddr signature[0]),
|
||||
signature.len)
|
||||
|
||||
result = resultCode == 1
|
||||
|
||||
proc ecSign*(message: string, alg: JwtAlgorithm, key: JWK): string =
|
||||
## Sign a message using the ECDSA algorithm.
|
||||
##
|
||||
## *key* is expected to be a `JWK <,./../jwt_full/jwk.html#JWK>`_
|
||||
|
||||
if key.keyKind != JwkKeyType.EcPrivate:
|
||||
raise newException(ValueError,
|
||||
$alg & " requires an ECDSA private key (\"type\"=\"EC\"), not a " &
|
||||
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
|
||||
|
||||
return bearEcSign(message, alg, toEcPrivateKey(key))
|
||||
|
||||
proc ecSign*(message: string, alg: JwtAlgorithm, pemKey: string): string =
|
||||
## Sign a message using the ECDSA algorithm.
|
||||
##
|
||||
## *pemKey* is expected to be a PEM
|
||||
return bearEcSign(message, alg, toEcPrivateKey(pemKey))
|
||||
|
||||
proc ecVerify*(message, signature: string, alg: JwtAlgorithm, key: JWK): bool =
|
||||
## Verify the signature for a message using ECDSA.
|
||||
##
|
||||
## *key* is expected to be a `JWK <../../jwt_full/jwk.html#JWK>`_
|
||||
|
||||
if key.keyKind != JwkKeyType.EcPrivate and
|
||||
key.keyKind != JwkKeyType.EcPublic:
|
||||
raise newException(ValueError,
|
||||
$alg & " requires an ECDSA private key (\"type\"=\"EC\"), not a " &
|
||||
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
|
||||
|
||||
return bearEcVerify(message, signature, alg, toEcPublicKey(key))
|
||||
|
||||
proc ecVerify*(message, signature: string, alg: JwtAlgorithm, pemKey: string): bool =
|
||||
## Verify the signature for a message using ECDSA.
|
||||
##
|
||||
## *pemKey* is expected to be a PEM.
|
||||
return bearEcVerify(message, signature, alg, toEcPublicKey(pemKey))
|
||||
@@ -0,0 +1,44 @@
|
||||
import std/tables
|
||||
import bearssl
|
||||
|
||||
type
|
||||
HashAlgorithm* = enum SHA1, SHA256, SHA384, SHA512
|
||||
|
||||
HashCfg* = object
|
||||
alg*: HashAlgorithm
|
||||
vtable*: ptr HashClass
|
||||
oid*: cstring
|
||||
size*: int
|
||||
|
||||
let HASHES* = newTable[HashAlgorithm, HashCfg]([
|
||||
(SHA1, HashCfg(
|
||||
alg: SHA1,
|
||||
vtable: addr sha1Vtable,
|
||||
oid: HASH_OID_SHA1,
|
||||
size: sha1Size)),
|
||||
(SHA256, HashCfg(
|
||||
alg: SHA256,
|
||||
vtable: addr sha256Vtable,
|
||||
oid: HASH_OID_SHA256,
|
||||
size: sha256Size)),
|
||||
(SHA384, HashCfg(
|
||||
alg: SHA384,
|
||||
vtable: addr sha384Vtable,
|
||||
oid: HASH_OID_SHA384,
|
||||
size: sha384Size)),
|
||||
(SHA512, HashCfg(
|
||||
alg: SHA512,
|
||||
vtable: addr sha512Vtable,
|
||||
oid: HASH_OID_SHA512,
|
||||
size: sha512Size)),
|
||||
])
|
||||
|
||||
proc hash*(data: string, alg: HashAlgorithm): string =
|
||||
|
||||
var ctx: HashCompatContext
|
||||
var pCtx = cast[ptr ptr HashClass](addr ctx)
|
||||
let hashCfg = HASHES[alg]
|
||||
result = newString(hashCfg.size)
|
||||
hashCfg.vtable.init(pCtx)
|
||||
hashCfg.vtable.update(pCtx, unsafeAddr data[0], data.len)
|
||||
hashCfg.vtable.output(pCtx, addr result[0])
|
||||
@@ -0,0 +1,47 @@
|
||||
import std/logging
|
||||
|
||||
import bearssl
|
||||
|
||||
import ../../jwa
|
||||
import ../../jwk
|
||||
|
||||
import ../encoding
|
||||
|
||||
proc bearHMAC(message: string, alg: JwtAlgorithm, key: string): string =
|
||||
|
||||
var vtable: ptr HashClass
|
||||
var hashSize: int
|
||||
|
||||
case alg:
|
||||
of HS256: vtable = addr sha256Vtable; hashSize = sha256Size
|
||||
of HS384: vtable = addr sha384Vtable; hashSize = sha384Size
|
||||
of HS512: vtable = addr sha512Vtable; hashSize = sha512Size
|
||||
else: raise newException(ValueError,
|
||||
"Unsupported HMAC algorithm '" & $alg & "'")
|
||||
|
||||
if key.len < hashSize:
|
||||
warn( "Computing an HMAC with a small key (" & $key.len & " bytes). It " &
|
||||
"is recommended to use keys of at least " & $hashSize & " bytes for " &
|
||||
$alg)
|
||||
|
||||
var keyCtx: HmacKeyContext
|
||||
var hmacCtx: HmacContext
|
||||
|
||||
hmacKeyInit(addr keyCtx, vtable, key.cstring, key.len)
|
||||
hmacInit(addr hmacCtx, addr keyCtx, 0)
|
||||
hmacUpdate(addr hmacCtx, message.cstring, message.len)
|
||||
|
||||
let resLen = hmacSize(addr hmacCtx)
|
||||
result = newString(resLen)
|
||||
discard hmacOut(addr hmacCtx, addr result[0])
|
||||
|
||||
proc hmac*(message: string, alg: JwtAlgorithm, key: string): string =
|
||||
return bearHMAC(message, alg, key)
|
||||
|
||||
proc hmac*(message: string, alg: JwtAlgorithm, key: JWK): string =
|
||||
if key.keyKind != Octet:
|
||||
raise newException(ValueError,
|
||||
$alg & " requires an octet key (\"typ\"=\"oct\"), not a \"typ\"=\"" &
|
||||
$(key.keyKind) & "\" key.")
|
||||
|
||||
return bearHMAC(message, alg, b64UrlDecode(key.octKey.k))
|
||||
@@ -0,0 +1,108 @@
|
||||
import bearssl
|
||||
|
||||
# Taken from nim-bearssl/decls.nim
|
||||
{.pragma: bearSslFunc, cdecl, gcsafe, noSideEffect, raises: [].}
|
||||
|
||||
type BearPemDecoderCallback* = proc(keyCtx: pointer, data: pointer, dataLen: int) {.bearSslFunc.}
|
||||
|
||||
proc decodePem*(
|
||||
pem: string,
|
||||
expectedObjectName: string,
|
||||
keyCtx: pointer,
|
||||
callback: BearPemDecoderCallback
|
||||
) =
|
||||
|
||||
var pemCtx: PemDecoderContext
|
||||
pemDecoderInit(addr pemCtx)
|
||||
|
||||
var offset = 0
|
||||
var bytesRemaining = len(pem)
|
||||
var readingObj = false
|
||||
|
||||
while bytesRemaining > 0:
|
||||
let bytesRead = pemDecoderPush(addr pemCtx, unsafeAddr pem[offset], bytesRemaining)
|
||||
offset += bytesRead
|
||||
bytesRemaining -= bytesRead
|
||||
|
||||
case pemDecoderEvent(addr pemCtx):
|
||||
of PEM_BEGIN_OBJ:
|
||||
if readingObj:
|
||||
raise newException(ValueError,
|
||||
"Invalid PEM: saw a second BEGIN before seeing END.")
|
||||
|
||||
if pemDecoderName(addr pemCtx) != expectedObjectName:
|
||||
raise newException(ValueError,
|
||||
"Invalid PEM: expected BEGIN " & expectedObjectName &
|
||||
" but got BEGIN " & $pemDecoderName(addr pemCtx))
|
||||
|
||||
readingObj = true
|
||||
pemDecoderSetdest(addr pemCtx, callback, keyCtx)
|
||||
|
||||
of PEM_END_OBJ:
|
||||
if readingObj: readingObj = false
|
||||
|
||||
of PEM_ERROR: raise newException(ValueError, "Invalid PEM.")
|
||||
|
||||
else: continue
|
||||
|
||||
#[
|
||||
proc decodeKeyFromPem*(pem: string): JWK =
|
||||
var decoderCtx: PemDecoderContext
|
||||
pemDecoderInit(addr decoderCtx)
|
||||
|
||||
var bytesRead = 0
|
||||
var skCtx: SkeyDecoderContext
|
||||
var pkCtx: PkeyDecoderContext
|
||||
var keyCtx: pointer = nil
|
||||
var keyType: JwkKeyType
|
||||
|
||||
while bytesRead < len(pem):
|
||||
bytesRead += pemDecoderPush(
|
||||
addr decoderCtx,
|
||||
unsafeAddr pem[bytesRead],
|
||||
len(pem))
|
||||
|
||||
case pemDecoderEvent(addr decoderCtx):
|
||||
of PEM_BEGIN_OBJ:
|
||||
if keyCtx != nil:
|
||||
raise newException(ValueError, "Invalid PEM: saw a second BEGIN")
|
||||
|
||||
let objName = pemDecoderName(addr decoderCtx)
|
||||
if objName == "RSA PRIVATE KEY":
|
||||
skeyDecoderInit(addr skCtx)
|
||||
keyCtx = addr skCtx
|
||||
keyType = JwkKeyType.RsaPrivate
|
||||
if objName == "EC PRIVATE KEY":
|
||||
skeyDecoderInit(addr skCtx)
|
||||
keyCtx = addr skCtx
|
||||
keyType = JwkKeyType.EcPrivate
|
||||
elif objName == "RSA PUBLIC KEY":
|
||||
pkeyDecoderInit(addr pkCtx)
|
||||
keyCtx = addr pkCtx
|
||||
keyType = JwkKeyType.RsaPublic
|
||||
else:
|
||||
raise newException(ValueError,
|
||||
"Unrecognized PEM object: " & $pemDecoderName(addr decoderCtx))
|
||||
|
||||
of PEM_END_OBJ:
|
||||
if keyCtx == nil:
|
||||
raise newException(ValueError, "Saw END before BEGIN.")
|
||||
elif keyType == JwkKeyType.RsaPrivate:
|
||||
return nil
|
||||
|
||||
of PEM_ERROR:
|
||||
raise newException(ValueError, "Invalid PEM.")
|
||||
|
||||
else: discard nil
|
||||
|
||||
proc decodeRsaPem*(pem: string): JWK =
|
||||
var skCtx: SkeyDecoderContext
|
||||
skeyDecoderInit(addr skCtx)
|
||||
decodePem(
|
||||
pem,
|
||||
cast[proc(ctx: pointer, data: pointer, dataLen: int) {.bearSslFunc.}](skeyDecoderPush)
|
||||
addr skCtx)
|
||||
if skeyDecoderLastError(addr skCtx) != 0:
|
||||
raise newException(ValueError,
|
||||
"Provided PEM could not be decoded as a valid RSA private key.")
|
||||
]#
|
||||
@@ -0,0 +1,217 @@
|
||||
import std/options, std/tables
|
||||
import bearssl, bearssl_pkey_decoder
|
||||
|
||||
import ../../jwa
|
||||
import ../../jwk
|
||||
|
||||
import ../encoding
|
||||
|
||||
import ./hash
|
||||
import ./pem
|
||||
|
||||
type
|
||||
RsaPublicKeyObj = object
|
||||
n*, e*: string
|
||||
bearKey: RsaPublicKey
|
||||
|
||||
RsaPrivateKeyObj = object
|
||||
p*, q*, dp*, dq*, iq*: string
|
||||
bearKey: RsaPrivateKey
|
||||
|
||||
func initRsaPublicKeyObj(n, e: string): RsaPublicKeyObj =
|
||||
result = RsaPublicKeyObj(n: n, e: e)
|
||||
result.bearKey.n = cast[ptr char](result.n.cstring)
|
||||
result.bearKey.nlen = result.n.len
|
||||
result.bearKey.e = cast[ptr char](result.e.cstring)
|
||||
result.bearKey.elen = result.e.len
|
||||
|
||||
func initRsaPrivateKeyObj(nBitLen: int, p, q, dp, dq, iq: string): RsaPrivateKeyObj =
|
||||
result = RsaPrivateKeyObj(p: p, q: q, dp: dp, dq: dq, iq: iq)
|
||||
result.bearKey.nBitLen = cast[uint32](nBitLen)
|
||||
result.bearKey.p = cast[ptr char](result.p.cstring)
|
||||
result.bearKey.plen = result.p.len
|
||||
result.bearKey.q = cast[ptr char](result.q.cstring)
|
||||
result.bearKey.qlen = result.q.len
|
||||
result.bearKey.dp = cast[ptr char](result.dp.cstring)
|
||||
result.bearKey.dplen = result.dp.len
|
||||
result.bearKey.dq = cast[ptr char](result.dq.cstring)
|
||||
result.bearKey.dqlen = result.dq.len
|
||||
result.bearKey.iq = cast[ptr char](result.iq.cstring)
|
||||
result.bearKey.iqlen = result.iq.len
|
||||
|
||||
proc toRsaPublicKey(jwk: JWK): RsaPublicKeyObj =
|
||||
## Convert an RSA public key in JWK format to the wrapper for BearSSL's
|
||||
## RsaPublicKey struct.
|
||||
|
||||
if jwk.keyKind != JwkKeyType.RsaPublic and
|
||||
jwk.keyKind != JwkKeyType.RsaPrivate:
|
||||
raise newException(ValueError,
|
||||
"Can not extract RSA public key from the given JWT: keyKind is " &
|
||||
$jwk.keyKind)
|
||||
|
||||
return initRsaPublicKeyObj(
|
||||
n = b64UrlDecode(jwk.rsaPub.n),
|
||||
e = b64UrlDecode(jwk.rsaPub.e))
|
||||
|
||||
|
||||
proc toRsaPublicKey(pem: string): RsaPublicKeyObj =
|
||||
var pkCtx: PkeyDecoderContext
|
||||
pkeyDecoderInit(addr pkCtx)
|
||||
decodePem(pem, "PUBLIC KEY", addr pkCtx,
|
||||
cast[BearPemDecoderCallback](pkeyDecoderPush))
|
||||
|
||||
let k = pkCtx.key.rsa
|
||||
return initRsaPublicKeyObj($cstring(k.n), $cstring(k.e))
|
||||
|
||||
|
||||
proc toRsaPrivateKey(jwk: JWK): RsaPrivateKeyObj =
|
||||
## Convert an RSA private key in JWK format to the wrapper for BearSSL's
|
||||
## RsaPrivateKey struct.
|
||||
##
|
||||
## Note: there are two ways to represent an rsa private key[1][rsa-priv-rep]:
|
||||
## 1. The pair modulus and private exponent pair (*n*, *d*)
|
||||
## 2. The quintuple with the prime factors, reduced CRT exponents, and first
|
||||
## CRT coefficient (*p*, *q*, *dP*, *dQ*, *qInv*)
|
||||
##
|
||||
## The JWA standard requires that a JWK representing an RSA private key
|
||||
## provide rep. 1 (*n*, *d*) and optionally allows rep. 2 to allows be
|
||||
## included. BearSSL strictly uses rep. 2 in it's key structures.
|
||||
## **Currently, this library only supports keys that provide the parameters
|
||||
## for rep. 2.** Ideally this library would also support keys only containing
|
||||
## rep. 1, including the logic to derive rep. 2 from use in BearSSL. This is
|
||||
## a TODO.
|
||||
##
|
||||
## [NIST Special Publication 800-56B][NIST.SP.800-56Br2] provides a choice of
|
||||
## two algorithms to derive the prime factors *p* and *q* from the private
|
||||
## exponent *d*. With the prime factors known, the calculation *dP*, *dQ*,
|
||||
## and *qInv* are straighforward based on [their definitions in RFC8
|
||||
## 8017][rsa-apdx-a]
|
||||
##
|
||||
## [rsa-priv-rep]: https://datatracker.ietf.org/doc/html/rfc8017#section-3.2
|
||||
## [rsa-apdx-a]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2
|
||||
## [NIST.SP.800-56Br2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf
|
||||
|
||||
if jwk.keyKind != JwkKeyType.RsaPrivate:
|
||||
raise newException(ValueError,
|
||||
"Can not extract RSA private key from the given JWT: keyKind is " &
|
||||
$jwk.keyKind)
|
||||
|
||||
let sk = jwk.rsaPrv
|
||||
if sk.p.isNone or sk.q.isNone or sk.dp.isNone or sk.dq.isNone or sk.qi.isNone:
|
||||
raise newException(ValueError,
|
||||
"RSA private key must have values for: n, e, d, p, q, dp, dq, and qi")
|
||||
|
||||
let n = b64UrlDecode(sk.n)
|
||||
return initRsaPrivateKeyObj(
|
||||
nBitLen = n.len * 8,
|
||||
p = b64UrlDecode(sk.p.get),
|
||||
q = b64UrlDecode(sk.q.get),
|
||||
dp = b64UrlDecode(sk.dp.get),
|
||||
dq = b64UrlDecode(sk.dq.get),
|
||||
iq = b64UrlDecode(sk.qi.get))
|
||||
|
||||
proc toRsaPrivateKey(pem: string): RsaPrivateKeyObj =
|
||||
var skCtx: SkeyDecoderContext
|
||||
skeyDecoderInit(addr skCtx)
|
||||
decodePem(pem, "RSA PRIVATE KEY", addr skCtx,
|
||||
cast[BearPemDecoderCallback](skeyDecoderPush))
|
||||
|
||||
let k = skCtx.key.rsa
|
||||
|
||||
return initRsaPrivateKeyObj(
|
||||
cast[int](k.nBitLen),
|
||||
$cstring(k.p),
|
||||
$cstring(k.q),
|
||||
$cstring(k.dp),
|
||||
$cstring(k.dq),
|
||||
$cstring(k.iq))
|
||||
|
||||
proc getRsaHashCfg(alg: JwtAlgorithm): HashCfg =
|
||||
let hashAlg = case alg:
|
||||
of RS256: SHA256
|
||||
of RS384: SHA384
|
||||
of RS512: SHA512
|
||||
else: raise newException(ValueError,
|
||||
"Unsupported RSA signature algorithm '" & $alg & "'")
|
||||
|
||||
result = HASHES[hashAlg]
|
||||
|
||||
proc bearRsaSign(
|
||||
message: string,
|
||||
alg: JwtAlgorithm,
|
||||
key: RsaPrivateKeyObj
|
||||
): string =
|
||||
|
||||
let hashCfg = getRsaHashCfg(alg)
|
||||
let hashed = hash(message, hashCfg.alg)
|
||||
|
||||
let rsaSignImpl = rsaPkcs1SignGetDefault()
|
||||
result = newString((key.bearKey.nBitLen + 7) div 8)
|
||||
|
||||
let errCode = rsaSignImpl(
|
||||
cast[ptr char](hashCfg.oid),
|
||||
cast[ptr char](unsafeAddr hashed[0]),
|
||||
hashed.len,
|
||||
unsafeAddr key.bearKey,
|
||||
cast[ptr char](addr result[0]))
|
||||
|
||||
if errCode != 1: raise newException(Exception, "RSA signature failed")
|
||||
|
||||
proc bearRsaVerify(
|
||||
message, signature: string,
|
||||
alg: JwtAlgorithm,
|
||||
key: RsaPublicKeyObj
|
||||
): bool =
|
||||
|
||||
let hashCfg = getRsaHashCfg(alg)
|
||||
let hashed = hash(message, hashCfg.alg)
|
||||
|
||||
let rsaVerifyImpl = rsaPkcs1VrfyGetDefault()
|
||||
var recoveredHash = newString(hashCfg.size)
|
||||
|
||||
let errCode = rsaVerifyImpl(
|
||||
cast[ptr char](unsafeAddr signature[0]),
|
||||
signature.len,
|
||||
cast[ptr char](hashCfg.oid),
|
||||
hashed.len,
|
||||
unsafeAddr key.bearKey,
|
||||
cast[ptr char](addr recoveredHash[0]))
|
||||
|
||||
if errCode != 1: return false
|
||||
return hashed == recoveredHash
|
||||
|
||||
proc rsaSign*(message: string, alg: JwtAlgorithm, key: JWK): string =
|
||||
## Sign a message using the RSA PKCS#1 v1.5 algorithm.
|
||||
##
|
||||
## *key* is expected to be a `JWK <../../jwt_full/jwk.html#JWK>`_
|
||||
|
||||
if key.keyKind != JwkKeyType.RsaPrivate:
|
||||
raise newException(ValueError,
|
||||
$alg & " requires an RSA Private key (\"type\"=\"RSA\"), not a " &
|
||||
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
|
||||
|
||||
return bearRsaSign(message, alg, toRsaPrivateKey(key))
|
||||
|
||||
proc rsaSign*(message: string, alg: JwtAlgorithm, pemKey: string): string =
|
||||
## Sign a message using hte RSA PKCS#1 v1.5 algorithm.
|
||||
##
|
||||
## *pemKey* is expected to be an RSA private key in PEM format.
|
||||
return bearRsaSign(message, alg, toRsaPrivateKey(pemKey))
|
||||
|
||||
proc rsaVerify*(message, signature: string; alg: JwtAlgorithm, key: JWK): bool =
|
||||
## Verify the signature for a message using PKCS#1 v1.5 algorithm.
|
||||
##
|
||||
## *key* is expected to be a `JWK <../../jwt_full/jwk.html#JWK>`_
|
||||
|
||||
if key.keyKind != JwkKeyType.RsaPublic:
|
||||
raise newException(ValueError,
|
||||
$alg & " requires an RSA Public key (\"type\"=\"RSA\"), not a " &
|
||||
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
|
||||
|
||||
return bearRsaVerify(message, signature, alg, toRsaPublicKey(key))
|
||||
|
||||
proc rsaVerify*(message, signature: string; alg: JwtAlgorithm, pemKey: string): bool =
|
||||
## Verify the signature for a message using PKCS#1 v1.5 algorithm.
|
||||
##
|
||||
## *key* is expected to be an RSA public key in PEM format.
|
||||
return bearRsaVerify(message, signature, alg, toRsaPublicKey(pemKey))
|
||||
Reference in new issue
Block a user