Skip to content

Commit

Permalink
certs in pubkeyresponse
Browse files Browse the repository at this point in the history
  • Loading branch information
shreyav committed Mar 25, 2024
1 parent 42bfc3d commit 7a16e4f
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 24 deletions.
82 changes: 76 additions & 6 deletions uma/protocol/pub_key_response.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,93 @@
package protocol

import "encoding/hex"
import (
"encoding/hex"
"encoding/json"
"errors"
"github.com/uma-universal-money-address/uma-go-sdk/uma/utils"
)

// PubKeyResponse is sent from a VASP to another VASP to provide its public keys.
// It is the response to GET requests at `/.well-known/lnurlpubkey`.
type PubKeyResponse struct {
// SigningCertChain is the PEM-encoded certificate chain used to verify signatures from a VASP.
SigningCertChain *string
// EncryptionCertChain is the PEM-encoded certificate chain used to encrypt TR info sent to a VASP.
EncryptionCertChain *string
// SigningPubKeyHex is used to verify signatures from a VASP. Hex-encoded byte array.
SigningPubKeyHex string `json:"signingPubKey"`
SigningPubKeyHex *string
// EncryptionPubKeyHex is used to encrypt TR info sent to a VASP. Hex-encoded byte array.
EncryptionPubKeyHex string `json:"encryptionPubKey"`
EncryptionPubKeyHex *string
// ExpirationTimestamp [Optional] Seconds since epoch at which these pub keys must be refreshed.
// They can be safely cached until this expiration (or forever if null).
ExpirationTimestamp *int64 `json:"expirationTimestamp"`
ExpirationTimestamp *int64
}

func (r *PubKeyResponse) SigningPubKey() ([]byte, error) {
return hex.DecodeString(r.SigningPubKeyHex)
if r.SigningCertChain != nil {
publicKey, err := utils.ExtractPubkeyFromPemCertificateChain(r.SigningCertChain)
if err != nil {
return nil, err
}
return publicKey.SerializeUncompressed(), nil
} else if r.SigningPubKeyHex != nil {
return hex.DecodeString(*r.SigningPubKeyHex)
} else {
return nil, errors.New("signingPubKeyHex is nil")
}
}

func (r *PubKeyResponse) EncryptionPubKey() ([]byte, error) {
return hex.DecodeString(r.EncryptionPubKeyHex)
if r.EncryptionCertChain != nil {
publicKey, err := utils.ExtractPubkeyFromPemCertificateChain(r.EncryptionCertChain)
if err != nil {
return nil, err
}
return publicKey.SerializeUncompressed(), nil
} else if r.EncryptionPubKeyHex == nil {
return hex.DecodeString(*r.EncryptionPubKeyHex)
} else {
return nil, errors.New("encryptionPubKeyHex is nil")
}
}

func (r *PubKeyResponse) MarshalJSON() ([]byte, error) {
signingCertChainHexDer, err := utils.ConvertPemCertificateChainToHexEncodedDer(r.SigningCertChain)
if err != nil {
return nil, err
}
encryptionCertChainHexDer, err := utils.ConvertPemCertificateChainToHexEncodedDer(r.EncryptionCertChain)
if err != nil {
return nil, err
}
m := map[string]interface{}{
"signingCertChain": signingCertChainHexDer,
"encryptionCertChain": encryptionCertChainHexDer,
"signingPubKey": r.SigningPubKeyHex,
"encryptionPubKey": r.EncryptionPubKeyHex,
"expirationTimestamp": r.ExpirationTimestamp,
}
return json.Marshal(m)
}

func (r *PubKeyResponse) UnmarshalJSON(data []byte) error {
var temp struct {
SigningCertChainHexDer *[]string `json:"signingCertChain"`
EncryptionCertChainHexDer *[]string `json:"encryptionCertChain"`
SigningPubKeyHex string `json:"signingPubKey"`
EncryptionPubKeyHex string `json:"encryptionPubKey"`
ExpirationTimestamp *int64 `json:"expirationTimestamp"`
}

if err := json.Unmarshal(data, &temp); err != nil {
return err
}

r.SigningCertChain, _ = utils.ConvertHexEncodedDerToPemCertChain(temp.SigningCertChainHexDer)
r.EncryptionCertChain, _ = utils.ConvertHexEncodedDerToPemCertChain(temp.EncryptionCertChainHexDer)
r.SigningPubKeyHex = &temp.SigningPubKeyHex
r.EncryptionPubKeyHex = &temp.EncryptionPubKeyHex
r.ExpirationTimestamp = temp.ExpirationTimestamp

return nil
}
66 changes: 65 additions & 1 deletion uma/test/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package uma_test

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
"github.com/uma-universal-money-address/uma-go-sdk/uma"
umaprotocol "github.com/uma-universal-money-address/uma-go-sdk/uma/protocol"
"testing"
)

func TestParseV0Currency(t *testing.T) {
Expand Down Expand Up @@ -187,3 +189,65 @@ func TestParseV0PayReqResponse(t *testing.T) {
require.Equal(t, "https://example.com/utxo", *(compliance.UtxoCallback))
require.Equal(t, 0, payReqResp.UmaMajorVersion)
}

func TestEncodeAndParsePubKeyResponse(t *testing.T) {
pubKeyHex := "04419c5467ea563f0010fd614f85e885ac99c21b8e8d416241175fdd5efd2244fe907e2e6fa3dd6631b1b17cd28798da8d882a34c4776d44cc4090781c7aadea1b"
pemCertChain := `-----BEGIN CERTIFICATE-----
MIIB1zCCAXygAwIBAgIUGN3ihBj1RnKoeTM/auDFnNoThR4wCgYIKoZIzj0EAwIw
QjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCmNhbGlmb3JuaWExDjAMBgNVBAcMBWxv
cyBhMQ4wDAYDVQQKDAVsaWdodDAeFw0yNDAzMDUyMTAzMTJaFw0yNDAzMTkyMTAz
MTJaMEIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApjYWxpZm9ybmlhMQ4wDAYDVQQH
DAVsb3MgYTEOMAwGA1UECgwFbGlnaHQwVjAQBgcqhkjOPQIBBgUrgQQACgNCAARB
nFRn6lY/ABD9YU+F6IWsmcIbjo1BYkEXX91e/SJE/pB+Lm+j3WYxsbF80oeY2o2I
KjTEd21EzECQeBx6reobo1MwUTAdBgNVHQ4EFgQUU87LnQdiP6XIE6LoKU1PZnbt
bMwwHwYDVR0jBBgwFoAUU87LnQdiP6XIE6LoKU1PZnbtbMwwDwYDVR0TAQH/BAUw
AwEB/zAKBggqhkjOPQQDAgNJADBGAiEAvsrvoeo3rbgZdTHxEUIgP0ArLyiO34oz
NlwL4gk5GpgCIQCvRx4PAyXNV9T6RRE+3wFlqwluOc/pPOjgdRw/wpoNPQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICdjCCAV6gAwIBAgIUAekCcU1Qhjo2Y6L2Down9BLdfdUwDQYJKoZIhvcNAQEL
BQAwNDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAmNhMQwwCgYDVQQHDANsb3MxCjAI
BgNVBAoMAWEwHhcNMjQwMzA4MDEwNTU3WhcNMjUwMzA4MDEwNTU3WjBAMQswCQYD
VQQGEwJVUzELMAkGA1UECAwCY2ExDDAKBgNVBAcMA2xvczEKMAgGA1UECgwBYTEK
MAgGA1UECwwBYTBWMBAGByqGSM49AgEGBSuBBAAKA0IABJ11ZAQKylgIzZmuI5NE
+DyZ9BUDZhxUPSxTxl+s1am+Lxzr9D7wlwOiiqCYHFWpL6lkCmJcCC06P3RyzXIT
KmyjQjBAMB0GA1UdDgQWBBRXgW6xGB3+mTSSUKlhSiu3LS+TKTAfBgNVHSMEGDAW
gBTFmyv7+YDpK0WAOHJYAzjynmWsMDANBgkqhkiG9w0BAQsFAAOCAQEAFVAA3wo+
Hi/k+OWO/1CFqIRV/0cA8F05sBMiKVA11xB6I1y54aUV4R0jN76fOiN1jnZqTRnM
G8rZUfQgE/LPVbb1ERHQfd8yaeI+TerKdPkMseu/jnvI+dDJfQdsY7iaa7NPO0dm
t8Nz75cYW8kYuDaq0Hb6uGsywf9LGO/VjrDhyiRxmZ1Oq4JxQmLuh5SDcPfqHTR3
VbMC1b7eVXaA9O2qYS36zv8cCUSUl5sOSwM6moaFN+xLtVNJ6ZhKPNS2Gd8znhzZ
AQZcDDpXBO6ORNbhVk5A3X6eQX4Ek1HBTa3pcSUQomYAA9TIuVzL6DSot5GWS8Ek
usLY8crt6ys3KQ==
-----END CERTIFICATE-----
`
pubKeyResponse, err := uma.GetPubKeyResponse(pemCertChain, pemCertChain, nil)

Check failure on line 224 in uma/test/protocol_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: uma.GetPubKeyResponse
require.NoError(t, err)

pubKeyResponseJson, err := pubKeyResponse.MarshalJSON()
require.NoError(t, err)
pubKeyResponseJsonMap := make(map[string]interface{})
err = json.Unmarshal(pubKeyResponseJson, &pubKeyResponseJsonMap)
require.NoError(t, err)

reserializedPubKeyResponse := umaprotocol.PubKeyResponse{}
err = json.Unmarshal(pubKeyResponseJson, &reserializedPubKeyResponse)
require.NoError(t, err)
require.Equal(t, *pubKeyResponse, reserializedPubKeyResponse)

keysOnlyPubKeyResponse := umaprotocol.PubKeyResponse{
SigningPubKeyHex: &pubKeyHex,
EncryptionPubKeyHex: &pubKeyHex,
}

pubKeyResponseJson, err = keysOnlyPubKeyResponse.MarshalJSON()
require.NoError(t, err)
pubKeyResponseJsonMap = make(map[string]interface{})
err = json.Unmarshal(pubKeyResponseJson, &pubKeyResponseJsonMap)
require.NoError(t, err)

reserializedPubKeyResponse = umaprotocol.PubKeyResponse{}
err = json.Unmarshal(pubKeyResponseJson, &reserializedPubKeyResponse)
require.NoError(t, err)
require.Equal(t, keysOnlyPubKeyResponse, reserializedPubKeyResponse)
}
14 changes: 10 additions & 4 deletions uma/test/uma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,9 +674,8 @@ func TestParseAndEncodePayReqToQueryParams(t *testing.T) {
require.Equal(t, payreq, payreqReparsed)
}

func TestParseX509CertificateString(t *testing.T) {
pemCert := `
-----BEGIN CERTIFICATE-----
func TestCertificateUtils(t *testing.T) {
pemCert := `-----BEGIN CERTIFICATE-----
MIIB1zCCAXygAwIBAgIUGN3ihBj1RnKoeTM/auDFnNoThR4wCgYIKoZIzj0EAwIw
QjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCmNhbGlmb3JuaWExDjAMBgNVBAcMBWxv
cyBhMQ4wDAYDVQQKDAVsaWdodDAeFw0yNDAzMDUyMTAzMTJaFw0yNDAzMTkyMTAz
Expand Down Expand Up @@ -705,13 +704,20 @@ AQZcDDpXBO6ORNbhVk5A3X6eQX4Ek1HBTa3pcSUQomYAA9TIuVzL6DSot5GWS8Ek
usLY8crt6ys3KQ==
-----END CERTIFICATE-----
`
pubkey, err := utils.ExtractPubkeyFromPemCertificateChain(pemCert)
pubkey, err := utils.ExtractPubkeyFromPemCertificateChain(&pemCert)
require.NoError(t, err)

publicKeyBytes := pubkey.SerializeUncompressed()
expectedPublicKey, err := hex.DecodeString("04419c5467ea563f0010fd614f85e885ac99c21b8e8d416241175fdd5efd2244fe907e2e6fa3dd6631b1b17cd28798da8d882a34c4776d44cc4090781c7aadea1b")
require.NoError(t, err)
require.Equal(t, publicKeyBytes, expectedPublicKey)

hexDerCerts, err := utils.ConvertPemCertificateChainToHexEncodedDer(&pemCert)
require.NoError(t, err)
require.Len(t, hexDerCerts, 2)
newPemCert, err := utils.ConvertHexEncodedDerToPemCertChain(&hexDerCerts)
require.NoError(t, err)
require.Equal(t, *newPemCert, pemCert)
}

func createLnurlpRequest(t *testing.T, signingPrivateKey []byte) umaprotocol.LnurlpRequest {
Expand Down
66 changes: 53 additions & 13 deletions uma/utils/cert_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,76 @@ package utils
import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"errors"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"math/big"
"time"
)

func ExtractPubkeyFromPemCertificateChain(certChain string) (*secp256k1.PublicKey, error) {
block, _ := pem.Decode([]byte(certChain))
if block == nil {
return nil, errors.New("failed to parse certificate chain PEM")
func ConvertPemCertificateChainToHexEncodedDer(certChain *string) ([]string, error) {
if certChain == nil {
return []string{}, nil
}
asn1Data := block.Bytes
asn1Certs, err := getAsn1DataFromPemChain(certChain)
if err != nil {
return nil, err
}
var v []string
for _, block := range *asn1Certs {
v = append(v, hex.EncodeToString(block))
}
return v, nil
}

var v []*certificate
for len(asn1Data) > 0 {
cert := new(certificate)
var err error
asn1Data, err = asn1.Unmarshal(asn1Data, cert)
func ConvertHexEncodedDerToPemCertChain(hexDerCerts *[]string) (*string, error) {
if hexDerCerts == nil || len(*hexDerCerts) == 0 {
return nil, nil
}
var pemCertChain string
for _, hexDerCert := range *hexDerCerts {
derCert, err := hex.DecodeString(hexDerCert)
if err != nil {
return nil, err
}
v = append(v, cert)
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: derCert,
}
pemCertChain = pemCertChain + string(pem.EncodeToMemory(block))
}
return &pemCertChain, nil
}

if len(v) == 0 {
func ExtractPubkeyFromPemCertificateChain(certChain *string) (*secp256k1.PublicKey, error) {
asn1Certs, err := getAsn1DataFromPemChain(certChain)
if err != nil {
return nil, err
}
if len(*asn1Certs) == 0 {
return nil, errors.New("empty certificate chain")
}
cert := new(certificate)
_, err = asn1.Unmarshal((*asn1Certs)[0], cert)
if err != nil {
return nil, err
}
return parseToSecp256k1PublicKey(&cert.TBSCertificate.PublicKey)
}

return parseToSecp256k1PublicKey(&v[0].TBSCertificate.PublicKey)
func getAsn1DataFromPemChain(certChain *string) (*[][]byte, error) {
pemData := []byte(*certChain)
var v [][]byte
for len(pemData) > 0 {
var block *pem.Block
block, pemData = pem.Decode(pemData)
if block == nil {
return nil, errors.New("failed to decode PEM block")
}
v = append(v, block.Bytes)
}
return &v, nil
}

func parseToSecp256k1PublicKey(keyData *publicKeyInfo) (*secp256k1.PublicKey, error) {
Expand Down

0 comments on commit 7a16e4f

Please sign in to comment.