diff --git a/uma/test/uma_test.go b/uma/test/uma_test.go index a3a7501..0304283 100644 --- a/uma/test/uma_test.go +++ b/uma/test/uma_test.go @@ -9,6 +9,7 @@ import ( "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" + "github.com/uma-universal-money-address/uma-go-sdk/uma/utils" "math" "net/url" "strconv" @@ -673,6 +674,46 @@ func TestParseAndEncodePayReqToQueryParams(t *testing.T) { require.Equal(t, payreq, payreqReparsed) } +func TestParseX509CertificateString(t *testing.T) { + pemCert := ` +-----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----- +` + 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) +} + func createLnurlpRequest(t *testing.T, signingPrivateKey []byte) umaprotocol.LnurlpRequest { queryUrl, err := uma.GetSignedLnurlpRequestUrl(signingPrivateKey, "$bob@vasp2.com", "vasp1.com", true, nil) require.NoError(t, err) diff --git a/uma/utils/cert_utils.go b/uma/utils/cert_utils.go new file mode 100644 index 0000000..b8358b8 --- /dev/null +++ b/uma/utils/cert_utils.go @@ -0,0 +1,72 @@ +package utils + +import ( + "crypto/x509/pkix" + "encoding/asn1" + "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") + } + asn1Data := block.Bytes + + var v []*certificate + for len(asn1Data) > 0 { + cert := new(certificate) + var err error + asn1Data, err = asn1.Unmarshal(asn1Data, cert) + if err != nil { + return nil, err + } + v = append(v, cert) + } + + if len(v) == 0 { + return nil, errors.New("empty certificate chain") + } + + return parseToSec256K1PublicKey(&v[0].TBSCertificate.PublicKey) +} + +func parseToSec256K1PublicKey(keyData *publicKeyInfo) (*secp256k1.PublicKey, error) { + asn1Data := keyData.PublicKey.RightAlign() + return secp256k1.ParsePubKey(asn1Data) +} + +type certificate struct { + Raw asn1.RawContent + TBSCertificate tbsCertificate + SignatureAlgorithm pkix.AlgorithmIdentifier + SignatureValue asn1.BitString +} + +type tbsCertificate struct { + Raw asn1.RawContent + Version int `asn1:"optional,explicit,default:0,tag:0"` + SerialNumber *big.Int + SignatureAlgorithm pkix.AlgorithmIdentifier + Issuer asn1.RawValue + Validity validity + Subject asn1.RawValue + PublicKey publicKeyInfo + UniqueId asn1.BitString `asn1:"optional,tag:1"` + SubjectUniqueId asn1.BitString `asn1:"optional,tag:2"` + Extensions []pkix.Extension `asn1:"optional,explicit,tag:3"` +} + +type publicKeyInfo struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +type validity struct { + NotBefore, NotAfter time.Time +}