diff --git a/integration/nwo/fabricconfig/core.go b/integration/nwo/fabricconfig/core.go index 771bee135a4..461c5213369 100644 --- a/integration/nwo/fabricconfig/core.go +++ b/integration/nwo/fabricconfig/core.go @@ -146,6 +146,7 @@ type Authentication struct { type BCCSP struct { Default string `yaml:"Default,omitempty"` SW *SoftwareProvider `yaml:"SW,omitempty"` + PKCS11 *PKCS11 `yaml:"PKCS11,omitempty"` } type SoftwareProvider struct { @@ -153,6 +154,14 @@ type SoftwareProvider struct { Security int `yaml:"Security,omitempty"` } +type PKCS11 struct { + Hash string `yaml:"Hash,omitempty"` + Security int `yaml:"Security,omitempty"` + Pin string `yaml:"Pin,omitempty"` + Label string `yaml:"Label,omitempty"` + Library string `yaml:"Library,omitempty"` +} + type DeliveryClient struct { ReconnectTotalTimeThreshold time.Duration `yaml:"reconnectTotalTimeThreshold,omitempty"` AddressOverrides []*AddressOverride `yaml:"addressOverrides,omitempty"` diff --git a/integration/nwo/network.go b/integration/nwo/network.go index 4f7114a855a..6a3c14cb5e9 100644 --- a/integration/nwo/network.go +++ b/integration/nwo/network.go @@ -375,6 +375,28 @@ func (n *Network) userCryptoDir(org *Organization, nodeOrganizationType, user, c ) } +// PeerOrgCADir returns the path to the folder containing the CA certificate(s) and/or +// keys for the specified peer organization. +func (n *Network) PeerOrgCADir(o *Organization) string { + return filepath.Join( + n.CryptoPath(), + "peerOrganizations", + o.Domain, + "ca", + ) +} + +// OrdererOrgCADir returns the path to the folder containing the CA certificate(s) and/or +// keys for the specified orderer organization. +func (n *Network) OrdererOrgCADir(o *Organization) string { + return filepath.Join( + n.CryptoPath(), + "ordererOrganizations", + o.Domain, + "ca", + ) +} + // PeerUserMSPDir returns the path to the MSP directory containing the // certificates and keys for the specified user of the peer. func (n *Network) PeerUserMSPDir(p *Peer, user string) string { diff --git a/integration/pkcs11/p11key.go b/integration/pkcs11/p11key.go new file mode 100644 index 00000000000..f41571aa372 --- /dev/null +++ b/integration/pkcs11/p11key.go @@ -0,0 +1,62 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pkcs11 + +import ( + "crypto" + "crypto/ecdsa" + "encoding/asn1" + "fmt" + "io" + "math/big" + + "github.com/miekg/pkcs11" +) + +// P11ECDSAKey test implementation of crypto.Signer. +type P11ECDSAKey struct { + ctx *pkcs11.Ctx + session pkcs11.SessionHandle + publicKey *ecdsa.PublicKey + privateKeyHandle pkcs11.ObjectHandle +} + +// Public returns the corresponding public key for the +// private key. +func (k *P11ECDSAKey) Public() crypto.PublicKey { + return k.publicKey +} + +// Sign implements crypto.Signer Sign(). Signs the digest the with the private key and returns a byte signature. +func (k *P11ECDSAKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { + if len(digest) != opts.HashFunc().Size() { + return nil, fmt.Errorf("digest length does not equal hash function length") + } + + mech := []*pkcs11.Mechanism{ + pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil), + } + + err = k.ctx.SignInit(k.session, mech, k.privateKeyHandle) + if err != nil { + return nil, fmt.Errorf("sign init failed: %s", err) + } + + signature, err = k.ctx.Sign(k.session, digest) + if err != nil { + return nil, fmt.Errorf("sign failed: %s", err) + } + + type ECDSASignature struct{ R, S *big.Int } + + R := new(big.Int) + S := new(big.Int) + R.SetBytes(signature[0 : len(signature)/2]) + S.SetBytes(signature[len(signature)/2:]) + + return asn1.Marshal(ECDSASignature{R: R, S: S}) +} diff --git a/integration/pkcs11/pkcs11_suite_test.go b/integration/pkcs11/pkcs11_suite_test.go new file mode 100644 index 00000000000..cc89e08cde3 --- /dev/null +++ b/integration/pkcs11/pkcs11_suite_test.go @@ -0,0 +1,89 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pkcs11 + +import ( + "encoding/json" + "testing" + + bpkcs11 "github.com/hyperledger/fabric/bccsp/pkcs11" + "github.com/hyperledger/fabric/integration/nwo" + "github.com/hyperledger/fabric/integration/nwo/commands" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" +) + +func TestPKCS11(t *testing.T) { + RegisterFailHandler(Fail) + lib, pin, label := bpkcs11.FindPKCS11Lib() + if lib == "" || pin == "" || label == "" { + t.Skip("Skipping PKCS11 Suite: Required ENV variables not set") + } + RunSpecs(t, "PKCS11 Suite") +} + +var components *nwo.Components + +var _ = SynchronizedBeforeSuite(func() []byte { + components = &nwo.Components{} + components.Build("-tags=pkcs11") + + payload, err := json.Marshal(components) + Expect(err).NotTo(HaveOccurred()) + + return payload +}, func(payload []byte) { + err := json.Unmarshal(payload, &components) + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = SynchronizedAfterSuite(func() { +}, func() { + components.Cleanup() +}) + +func StartPort() int { + return 23000 + 300*GinkgoParallelNode() +} + +func runQueryInvokeQuery(n *nwo.Network, orderer *nwo.Orderer, peer *nwo.Peer, channel string) { + By("querying the chaincode") + sess, err := n.PeerUserSession(peer, "User1", commands.ChaincodeQuery{ + ChannelID: channel, + Name: "mycc", + Ctor: `{"Args":["query","a"]}`, + }) + Expect(err).NotTo(HaveOccurred()) + Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0)) + Expect(sess).To(gbytes.Say("100")) + + sess, err = n.PeerUserSession(peer, "User1", commands.ChaincodeInvoke{ + ChannelID: channel, + Orderer: n.OrdererAddress(orderer, nwo.ListenPort), + Name: "mycc", + Ctor: `{"Args":["invoke","a","b","10"]}`, + PeerAddresses: []string{ + n.PeerAddress(n.Peer("Org1", "peer0"), nwo.ListenPort), + n.PeerAddress(n.Peer("Org2", "peer0"), nwo.ListenPort), + }, + WaitForEvent: true, + }) + Expect(err).NotTo(HaveOccurred()) + Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0)) + Expect(sess.Err).To(gbytes.Say("Chaincode invoke successful. result: status:200")) + + sess, err = n.PeerUserSession(peer, "User1", commands.ChaincodeQuery{ + ChannelID: channel, + Name: "mycc", + Ctor: `{"Args":["query","a"]}`, + }) + Expect(err).NotTo(HaveOccurred()) + Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0)) + Expect(sess).To(gbytes.Say("90")) +} diff --git a/integration/pkcs11/pkcs11_test.go b/integration/pkcs11/pkcs11_test.go new file mode 100644 index 00000000000..b067ea0505c --- /dev/null +++ b/integration/pkcs11/pkcs11_test.go @@ -0,0 +1,419 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package pkcs11 + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/hex" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "syscall" + "time" + + docker "github.com/fsouza/go-dockerclient" + bpkcs11 "github.com/hyperledger/fabric/bccsp/pkcs11" + "github.com/hyperledger/fabric/integration/nwo" + "github.com/hyperledger/fabric/integration/nwo/fabricconfig" + "github.com/miekg/pkcs11" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/tedsuo/ifrit" +) + +var _ = Describe("PKCS11 enabled network", func() { + var ( + tempDir string + client *docker.Client + network *nwo.Network + process ifrit.Process + ) + + BeforeEach(func() { + var err error + tempDir, err = ioutil.TempDir("", "p11") + Expect(err).NotTo(HaveOccurred()) + + client, err = docker.NewClientFromEnv() + Expect(err).NotTo(HaveOccurred()) + + network = nwo.New(nwo.BasicSolo(), tempDir, client, StartPort(), components) + network.GenerateConfigTree() + network.Bootstrap() + + By("configuring PKCS11 artifacts") + setupPKCS11(network) + + By("starting fabric processes") + networkRunner := network.NetworkGroupRunner() + process = ifrit.Invoke(networkRunner) + Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed()) + }) + + AfterEach(func() { + if process != nil { + process.Signal(syscall.SIGTERM) + Eventually(process.Wait(), network.EventuallyTimeout).Should(Receive()) + } + network.Cleanup() + os.RemoveAll(tempDir) + }) + + It("executes transactions against a basic solo network", func() { + chaincode := nwo.Chaincode{ + Name: "mycc", + Version: "0.0", + Path: "github.com/hyperledger/fabric/integration/chaincode/simple/cmd", + Ctor: `{"Args":["init","a","100","b","200"]}`, + Policy: `AND ('Org1MSP.member','Org2MSP.member')`, + } + + orderer := network.Orderer("orderer") + network.CreateAndJoinChannels(orderer) + + nwo.DeployChaincode(network, "testchannel", orderer, chaincode) + runQueryInvokeQuery(network, orderer, network.Peer("Org1", "peer0"), "testchannel") + }) +}) + +func setupPKCS11(network *nwo.Network) { + lib, pin, label := bpkcs11.FindPKCS11Lib() + + By("establishing a PKCS11 session") + ctx, sess := setupPKCS11Ctx(lib, label, pin) + defer ctx.Destroy() + defer ctx.CloseSession(sess) + + configurePeerPKCS11(ctx, sess, network) + configureOrdererPKCS11(ctx, sess, network) + + bccspConfig := &fabricconfig.BCCSP{ + Default: "PKCS11", + PKCS11: &fabricconfig.PKCS11{ + Security: 256, + Hash: "SHA2", + Pin: pin, + Label: label, + Library: lib, + }, + } + + By("updating bccsp peer config") + for _, peer := range network.Peers { + peerConfig := network.ReadPeerConfig(peer) + peerConfig.Peer.BCCSP = bccspConfig + network.WritePeerConfig(peer, peerConfig) + } + + By("updating bccsp orderer config") + orderer := network.Orderer("orderer") + ordererConfig := network.ReadOrdererConfig(orderer) + ordererConfig.General.BCCSP = bccspConfig + network.WriteOrdererConfig(orderer, ordererConfig) +} + +func configurePeerPKCS11(ctx *pkcs11.Ctx, sess pkcs11.SessionHandle, network *nwo.Network) { + for _, peer := range network.Peers { + orgName := peer.Organization + + peerPubKey, peerCSR, peerSerial := createCSR(ctx, sess, orgName, "peer") + adminPubKey, adminCSR, adminSerial := createCSR(ctx, sess, orgName, "admin") + userPubKey, userCSR, userSerial := createCSR(ctx, sess, orgName, "client") + + domain := network.Organization(orgName).Domain + + // Retrieves org CA cert + orgCAPath := network.PeerOrgCADir(network.Organization(orgName)) + caBytes, err := ioutil.ReadFile(filepath.Join(orgCAPath, fmt.Sprintf("ca.%s-cert.pem", domain))) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the peer signcerts") + newOrdererPemCert := buildCert(caBytes, orgCAPath, peerCSR, peerSerial, peerPubKey) + updateMSPFolder(network.PeerLocalMSPDir(peer), fmt.Sprintf("peer.%s-cert.pem", domain), newOrdererPemCert) + + By("Updating the peer admin user signcerts") + newAdminPemCert := buildCert(caBytes, orgCAPath, adminCSR, adminSerial, adminPubKey) + orgAdminMSPPath := network.PeerUserMSPDir(peer, "Admin") + updateMSPFolder(orgAdminMSPPath, fmt.Sprintf("Admin@%s-cert.pem", domain), newAdminPemCert) + + By("Updating the peer user1 signcerts") + newUserPemCert := buildCert(caBytes, orgCAPath, userCSR, userSerial, userPubKey) + orgUserMSPPath := network.PeerUserMSPDir(peer, "User1") + updateMSPFolder(orgUserMSPPath, fmt.Sprintf("User1@%s-cert.pem", domain), newUserPemCert) + } +} + +func configureOrdererPKCS11(ctx *pkcs11.Ctx, sess pkcs11.SessionHandle, network *nwo.Network) { + orderer := network.Orderer("orderer") + orgName := orderer.Organization + domain := network.Organization(orgName).Domain + + ordererPubKey, ordererCSR, ordererSerial := createCSR(ctx, sess, orgName, "orderer") + adminPubKey, adminCSR, adminSerial := createCSR(ctx, sess, orgName, "admin") + + // Retrieves org CA cert + orgCAPath := network.OrdererOrgCADir(network.Organization(orgName)) + caBytes, err := ioutil.ReadFile(filepath.Join(orgCAPath, fmt.Sprintf("ca.%s-cert.pem", domain))) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the orderer signcerts") + newOrdererPemCert := buildCert(caBytes, orgCAPath, ordererCSR, ordererSerial, ordererPubKey) + updateMSPFolder(network.OrdererLocalMSPDir(orderer), fmt.Sprintf("orderer.%s-cert.pem", domain), newOrdererPemCert) + + By("Updating the orderer admin user signcerts") + newAdminPemCert := buildCert(caBytes, orgCAPath, adminCSR, adminSerial, adminPubKey) + orgAdminMSPPath := network.OrdererUserMSPDir(orderer, "Admin") + updateMSPFolder(orgAdminMSPPath, fmt.Sprintf("Admin@%s-cert.pem", domain), newAdminPemCert) +} + +// Creates pkcs11 context and session +func setupPKCS11Ctx(lib, label, pin string) (*pkcs11.Ctx, pkcs11.SessionHandle) { + ctx := pkcs11.New(lib) + + err := ctx.Initialize() + Expect(err).NotTo(HaveOccurred()) + + slot := findPKCS11Slot(ctx, label) + Expect(slot).Should(BeNumerically(">", 0), "Could not find slot with label %s", label) + + sess, err := ctx.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) + Expect(err).NotTo(HaveOccurred()) + + // Login + err = ctx.Login(sess, pkcs11.CKU_USER, pin) + Expect(err).NotTo(HaveOccurred()) + + return ctx, sess +} + +// Identifies pkcs11 slot using specified label +func findPKCS11Slot(ctx *pkcs11.Ctx, label string) uint { + slots, err := ctx.GetSlotList(true) + Expect(err).NotTo(HaveOccurred()) + + for _, s := range slots { + tokInfo, err := ctx.GetTokenInfo(s) + Expect(err).NotTo(HaveOccurred()) + + if tokInfo.Label == label { + return s + } + } + + return 0 +} + +// Creates CSR for provided organization and organizational unit +func createCSR(ctx *pkcs11.Ctx, sess pkcs11.SessionHandle, org, ou string) (*ecdsa.PublicKey, *x509.CertificateRequest, *big.Int) { + pubKey, pkcs11Key := generateKeyPair(ctx, sess) + + csrTemplate := x509.CertificateRequest{ + Subject: pkix.Name{ + Country: []string{"US"}, + Province: []string{"California"}, + Locality: []string{"San Francisco"}, + Organization: []string{fmt.Sprintf("%s.example.com", org)}, + OrganizationalUnit: []string{ou}, + CommonName: fmt.Sprintf("peer.%s.example.com", org), + }, + SignatureAlgorithm: x509.ECDSAWithSHA256, + } + + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, pkcs11Key) + Expect(err).NotTo(HaveOccurred()) + + csr, err := x509.ParseCertificateRequest(csrBytes) + Expect(err).NotTo(HaveOccurred()) + err = csr.CheckSignature() + Expect(err).NotTo(HaveOccurred()) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + Expect(err).NotTo(HaveOccurred()) + + return pubKey, csr, serialNumber +} + +func buildCert(caBytes []byte, orgCAPath string, csr *x509.CertificateRequest, serialNumber *big.Int, pubKey *ecdsa.PublicKey) []byte { + pemBlock, _ := pem.Decode(caBytes) + Expect(pemBlock).NotTo(BeNil()) + + caCert, err := x509.ParseCertificate(pemBlock.Bytes) + Expect(err).NotTo(HaveOccurred()) + + pk, ok := caCert.PublicKey.(*ecdsa.PublicKey) + Expect(ok).To(BeTrue()) + + keyBytes, err := ioutil.ReadFile(filepath.Join(orgCAPath, fmt.Sprintf("%x_sk", computeSKI(pk)))) + Expect(err).NotTo(HaveOccurred()) + + pemBlock, _ = pem.Decode(keyBytes) + Expect(pemBlock).NotTo(BeNil()) + key, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) + Expect(err).NotTo(HaveOccurred()) + caKey := key.(*ecdsa.PrivateKey) + + certTemplate := &x509.Certificate{ + Signature: csr.Signature, + SignatureAlgorithm: csr.SignatureAlgorithm, + PublicKey: csr.PublicKey, + PublicKeyAlgorithm: csr.PublicKeyAlgorithm, + + SerialNumber: serialNumber, + NotBefore: time.Now().Add(-1 * time.Minute).UTC(), + NotAfter: time.Now().Add(365 * 24 * time.Hour).UTC(), + BasicConstraintsValid: true, + + Subject: csr.Subject, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{}, + } + + // Use root CA to create and sign cert + signedCert, err := x509.CreateCertificate(rand.Reader, certTemplate, caCert, pubKey, caKey) + Expect(err).NotTo(HaveOccurred()) + + return pem.EncodeToMemory(&pem.Block{Bytes: signedCert, Type: "CERTIFICATE"}) +} + +// Overwrites existing cert and removes private key from keystore folder +func updateMSPFolder(path, certName string, cert []byte) { + // Overwrite existing certificate with new certificate + err := ioutil.WriteFile(filepath.Join(path, "signcerts", certName), cert, 0644) + Expect(err).NotTo(HaveOccurred()) + + // delete the existing private key - this is stored in the hsm + matches, err := filepath.Glob(filepath.Join(path, "keystore", "*_sk")) + Expect(err).NotTo(HaveOccurred()) + for _, match := range matches { + err := os.Remove(match) + Expect(err).NotTo(HaveOccurred()) + } +} + +// Generating key pair in HSM, convert, and return keys +func generateKeyPair(ctx *pkcs11.Ctx, sess pkcs11.SessionHandle) (*ecdsa.PublicKey, *P11ECDSAKey) { + publabel, privlabel := "BCPUB7", "BCPRV7" + + curve := asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} // secp256r1 Curve + + marshaledOID, err := asn1.Marshal(curve) + Expect(err).NotTo(HaveOccurred()) + + pubAttrs := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC), + pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), + pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), + pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true), + pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, marshaledOID), + + pkcs11.NewAttribute(pkcs11.CKA_ID, publabel), + pkcs11.NewAttribute(pkcs11.CKA_LABEL, publabel), + } + + privAttrs := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC), + pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), + pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), + pkcs11.NewAttribute(pkcs11.CKA_SIGN, true), + + pkcs11.NewAttribute(pkcs11.CKA_ID, privlabel), + pkcs11.NewAttribute(pkcs11.CKA_LABEL, privlabel), + + pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false), + pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true), + } + + pubK, privK, err := ctx.GenerateKeyPair( + sess, + []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_EC_KEY_PAIR_GEN, nil)}, + pubAttrs, + privAttrs, + ) + Expect(err).NotTo(HaveOccurred()) + + ecpt := ecPoint(ctx, sess, pubK) + Expect(ecpt).NotTo(BeEmpty(), "CKA_EC_POINT not found") + + hash := sha256.Sum256(ecpt) + ski := hash[:] + + setskiT := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_ID, ski), + pkcs11.NewAttribute(pkcs11.CKA_LABEL, hex.EncodeToString(ski)), + } + + err = ctx.SetAttributeValue(sess, pubK, setskiT) + Expect(err).NotTo(HaveOccurred()) + + err = ctx.SetAttributeValue(sess, privK, setskiT) + Expect(err).NotTo(HaveOccurred()) + + // convert pub key to rsa types + nistCurve := elliptic.P256() + x, y := elliptic.Unmarshal(nistCurve, ecpt) + if x == nil { + Expect(x).NotTo(BeNil(), "Failed Unmarshaling Public Key") + } + + pubKey := &ecdsa.PublicKey{Curve: nistCurve, X: x, Y: y} + + pkcs11Key := &P11ECDSAKey{ + ctx: ctx, + session: sess, + publicKey: pubKey, + privateKeyHandle: privK, + } + + return pubKey, pkcs11Key +} + +// SoftHSM reports extra two bytes before the uncompressed point +// see /bccsp/pkcs11/pkcs11.go::ecPoint() for additional details +func ecPoint(pkcs11lib *pkcs11.Ctx, session pkcs11.SessionHandle, key pkcs11.ObjectHandle) (ecpt []byte) { + template := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, nil), + pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, nil), + } + + attr, err := pkcs11lib.GetAttributeValue(session, key, template) + if err != nil { + Expect(err).NotTo(HaveOccurred(), "PKCS11: get(EC point)") + } + + for _, a := range attr { + if a.Type != pkcs11.CKA_EC_POINT { + continue + } + + switch { + case ((len(a.Value) % 2) == 0) && (byte(0x04) == a.Value[0]) && (byte(0x04) == a.Value[len(a.Value)-1]): + ecpt = a.Value[0 : len(a.Value)-1] // Trim trailing 0x04 + case byte(0x04) == a.Value[0] && byte(0x04) == a.Value[2]: + ecpt = a.Value[2:len(a.Value)] + default: + ecpt = a.Value + } + } + + return ecpt +} + +func computeSKI(publicKey *ecdsa.PublicKey) []byte { + raw := elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y) + hash := sha256.Sum256(raw) + return hash[:] +}