Escrito por

Artigo Evandro Wendt · jan 6 3m read

Gerando JWT sem acesso aos certificados/chaves x509 do sistema

Se você quiser gerar um JWT a partir de um certificado/chave x509, qualquer operação (inclusive leitura) em %SYS.X509Credentials exige permissão U no recurso %Admin_Secure. O %Admin_Secure é necessário porque %SYS.X509Credentials é persistente e foi implementado dessa forma para impedir que todos os usuários tenham acesso às chaves privadas.

Se o recurso %Admin_Secure não estiver disponível em tempo de execução, você pode usar a seguinte alternativa.

Ao revisar o código de geração de JWT, descobri que o código de JWT utiliza %SYS.X509Credentials apenas como fonte de dados em tempo de execução para PrivateKey, PrivateKeyPassword, e Certificate. Como alternativa, você pode usar uma implementação não persistente da interface X.509 em tempo de execução, expondo apenas essas propriedades.Se você estiver usando interoperabilidade, o certificado/chave privada (Cert/PK) pode ser armazenado em credenciais para acesso seguro.

Class User.X509 Extends%RegisteredObject
{

Property PrivateKey As%VarString;Property PrivateKeyPassword As%String;Property Certificate As%VarString;Property HasPrivateKey As%Boolean [ InitialExpression = {$$$YES} ];ClassMethod GetX509() As User.X509
{
    set x509 = ..%New()
    set x509.PrivateKey = ..Key()
    set x509.Certificate = ..Cert()
    quit x509
}

/// Get X509 object from credential./// Username is a Cert, Password is a Private KeyClassMethod GetX509FromCredential(credential) As User.X509
{
    set credentialObj = ##class(Ens.Config.Credentials).%OpenId(credential,,.sc)
    throw:$$$ISERR(sc) ##class(%Exception.StatusException).ThrowIfInterrupt(sc)
    
    set x509 = ..%New()
    set x509.PrivateKey = credentialObj.Password
    set x509.Certificate = credentialObj.Username
    quit x509
}

ClassMethod Key()
{
    q"-----BEGIN RSA PRIVATE KEY-----"_$C(13,10)
    _"YOUR_TEST_KEY"_$C(13,10)
    _"-----END RSA PRIVATE KEY-----"
}

ClassMethod Cert() As%VarString
{
    q"-----BEGIN CERTIFICATE-----"_$C(13,10)
    _"YOUR_TEST_CERT"_$C(13,10)
    _"-----END CERTIFICATE-----"
}

}

E você pode gerar o JWT da seguinte forma:

ClassMethod JWT() As%Status
{
    Set sc = $$$OK//Set x509 = ##class(%SYS.X509Credentials).GetByAlias("TempKeyPair")Set x509 = ##class(User.X509).GetX509()
    
    Set algorithm ="RS256"Set header = {"alg": (algorithm), "typ": "JWT"}
    Set claims= {"Key": "Value" }
    
    #; create JWKSet sc = ##class(%Net.JSON.JWK).CreateX509(algorithm,x509,.privateJWK)
    
    If$$$ISERR(sc) {
        Write$SYSTEM.OBJ.DisplayError(sc)
    }

    #; Create JWKSSet sc = ##class(%Net.JSON.JWKS).PutJWK(privateJWK,.privateJWKS)
    
    If$$$ISERR(sc) {
        Write$SYSTEM.OBJ.DisplayError(sc)
    }

    Set sc = ##Class(%Net.JSON.JWT).Create(header,,claims,privateJWKS,,.pJWT)
    
    If$$$ISERR(sc) {
        Write$SYSTEM.OBJ.DisplayError(sc)
    }
    
    Write pJWT
	Return sc
}

Como alternativa, você pode usar um objeto dinâmico para evitar a criação de uma classe; nesse caso, ficaria assim:

ClassMethod JWT(credential) As%Status
{
    Set sc = $$$OK//Set x509 = ##class(%SYS.X509Credentials).GetByAlias("TempKeyPair")Set credentialObj = ##class(Ens.Config.Credentials).%OpenId(credential,,.sc)
    throw:$$$ISERR(sc) ##class(%Exception.StatusException).ThrowIfInterrupt(sc)
    
    Set x509 = {
        "HasPrivateKey": true,
        "PrivateKey": (credentialObj.Password),
        "PrivateKeyPassword":"",
        "Certificate":(credentialObj.Username)
    }

    Set algorithm ="RS256"Set header = {"alg": (algorithm), "typ": "JWT"}
    Set claims= {"Key": "Value" }
    
    #; create JWKSet sc = ##class(%Net.JSON.JWK).CreateX509(algorithm,x509,.privateJWK)
    
    If$$$ISERR(sc) {
        Write$SYSTEM.OBJ.DisplayError(sc)
    }

    #; Create JWKSSet sc = ##class(%Net.JSON.JWKS).PutJWK(privateJWK,.privateJWKS)
    
    If$$$ISERR(sc) {
        Write$SYSTEM.OBJ.DisplayError(sc)
    }

    Set sc = ##Class(%Net.JSON.JWT).Create(header,,claims,privateJWKS,,.pJWT)
    
    If$$$ISERR(sc) {
        Write$SYSTEM.OBJ.DisplayError(sc)
    }
    
    Write pJWT
    Return sc
}