diff --git a/post-quantum-server-testing/README.md b/post-quantum-server-testing/README.md index 1426eb9..a60c631 100644 --- a/post-quantum-server-testing/README.md +++ b/post-quantum-server-testing/README.md @@ -1 +1,87 @@ -This is a WIP \ No newline at end of file +# Post-Quantum Signature Generation Performance Test + +Benchmark signature generation performance for post-quantum algorithms ML-DSA-65, Falcon-512 and current algorithms RSA-4096, ECDSA-384. + +This is to help us investigate and understand performance issues we might run into while doing code-signing and content-signing. + +The test will execute 100 signature generations for 4 different algorithms each with 2 different payload sizes. Then return the signing time per operation of each algorithm. + +## Running the Testing Program + +### Requirements + +This program is meant to be a server-side test and can only be tested by authenticated GCP users, otherwise KMS signing will not work. + +**Note:** The ML-DSA-65 and ECDSA-384 test functions use KMS key names `mldsa` and `ecdsa`. If your KMS keys have different names, you'll need to update the key names in the `SignData()` function calls in `main.go`. + +### Linux (Ubuntu/Debian) + +#### 1. Install Dependencies + +```bash +sudo apt update +sudo apt install -y \ + git \ + cmake \ + build-essential \ + pkg-config \ + libssl-dev \ + golang-go +``` + +#### 2. Configure Environment Variables + +Create a `.env` file in the `src` directory with your own Google KMS details: + +```bash +LOCATION=global +KEYRING=your-keyring +PROJECT_ID=your-gcp-project-id +``` + +#### 3. Build and Install liboqs + +```bash +# Clone and install the liboqs using cmake +git clone --depth=1 https://github.com/open-quantum-safe/liboqs +cmake -S liboqs -B liboqs/build -DBUILD_SHARED_LIBS=ON +cmake --build liboqs/build --parallel 4 +sudo cmake --build liboqs/build --target install +``` +**Note:** Change `--parallel 4` to the amount of available cores on your system. + +#### 4. Set Up liboqs-go Wrapper + +```bash +# Clone the liboqs-go +git clone --depth=1 https://github.com/open-quantum-safe/liboqs-go + +# Next, you must modify the following lines in $HOME/liboqs-go/.config/liboqs-go.pc +LIBOQS_INCLUDE_DIR=/usr/local/include +LIBOQS_LIB_DIR=/usr/local/lib + +# Add these environment variables +export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$PWD/liboqs-go/.config +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib +``` + +**Note:** The `liboqs-go.pc` might already have the lines added + +#### 5. Authenticate with GCP + +```bash +gcloud auth application-default login +``` + +#### 6. Install Go Dependencies + +```bash +cd post-quantum-server-testing/src +go mod download +``` + +#### 7. Run the Program + +```bash +go run main.go +``` diff --git a/post-quantum-server-testing/src/.gitignore b/post-quantum-server-testing/src/.gitignore new file mode 100644 index 0000000..fb4099e --- /dev/null +++ b/post-quantum-server-testing/src/.gitignore @@ -0,0 +1,22 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov + +# Go workspace file +go.work +go.work.sum + +# env file +.env diff --git a/post-quantum-server-testing/src/ecdsa_local/ecdsa_local.go b/post-quantum-server-testing/src/ecdsa_local/ecdsa_local.go new file mode 100644 index 0000000..4db24bb --- /dev/null +++ b/post-quantum-server-testing/src/ecdsa_local/ecdsa_local.go @@ -0,0 +1,81 @@ +package ecdsa_local + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha512" + "crypto/x509" + "encoding/base64" + "fmt" + "log" + "sync" +) + +// Public key struct +type PublicKey struct { + Pk *ecdsa.PublicKey +} + +// Private key struct +type PrivateKey struct { + PublicKey + Sk *ecdsa.PrivateKey +} + +// Response struct +type response struct { + Signature string `json:"signature"` + PublicKey string `json:"publicKey"` +} + +// GenerateKey generates a new ECDSA P-384 key pair +func GenerateKey() (*PrivateKey, error) { + sk, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, err + } + pk := &sk.PublicKey + + privateKey := &PrivateKey{ + PublicKey: PublicKey{Pk: pk}, + Sk: sk, + } + return privateKey, nil +} + +// signPQC signs a message using the private key +func (priv *PrivateKey) signPQC(msg []byte) ([]byte, error) { + hash := sha512.Sum384(msg) + sign, err := ecdsa.SignASN1(rand.Reader, priv.Sk, hash[:]) + if err != nil { + return nil, fmt.Errorf("signing failed: %v", err) + } + return sign, nil +} + +// SignData signs input and returns a response with signature and public key +func SignData(input string, privKey *PrivateKey, wg *sync.WaitGroup) response { + defer wg.Done() + + message, err := base64.StdEncoding.DecodeString(input) + if err != nil { + log.Fatalf("error decoding base64 input %v", err) + } + + signature, err := privKey.signPQC(message) + if err != nil { + log.Fatalf("error signing message: %v", err) + } + + pubKey, err := x509.MarshalPKIXPublicKey(privKey.PublicKey.Pk) + if err != nil { + log.Fatalf("error marshaling public key: %v", err) + } + resp := response{ + Signature: base64.StdEncoding.EncodeToString(signature), + PublicKey: base64.StdEncoding.EncodeToString(pubKey), + } + + return resp +} diff --git a/post-quantum-server-testing/src/falcon/falcon.go b/post-quantum-server-testing/src/falcon/falcon.go new file mode 100644 index 0000000..08b5646 --- /dev/null +++ b/post-quantum-server-testing/src/falcon/falcon.go @@ -0,0 +1,103 @@ +package falcon + +import ( + "crypto/sha256" + "encoding/base64" + "fmt" + "log" + "sync" + + "github.com/open-quantum-safe/liboqs-go/oqs" +) + +const ( + SIGNAME = "Falcon-512" + PUBLICKEYSIZE = 897 + PRIVATEKEYSIZE = 1281 +) + +// Public key struct +type PublicKey struct { + Pk []byte +} + +// Private key struct +type PrivateKey struct { + PublicKey + Sk []byte +} + +// This is a struct for the response +type response struct { + Signature string `json:"signature"` + PublicKey string `json:"publicKey"` +} + +// This function generates a pivate/public key pair +func GenerateKey() (*PrivateKey, error) { + signer := oqs.Signature{} + + if err := signer.Init(SIGNAME, nil); err != nil { + log.Fatal(err) + } + + pk, err := signer.GenerateKeyPair() + if err != nil { + return nil, err + } + + privateKey := new(PrivateKey) + sk := signer.ExportSecretKey() + + privateKey.PublicKey.Pk = pk + privateKey.Sk = sk + + return privateKey, err +} + +// This function takes a private key, signs a message and returns a signature +func (priv *PrivateKey) SignPQC(msg []byte) (sig []byte, err error) { + signer := oqs.Signature{} + + if err := signer.Init(SIGNAME, priv.Sk); err != nil { + return nil, fmt.Errorf("failed to init signer: %w", err) + } + + // hash with sha256 + h := sha256.New() + h.Write(msg) + hash := h.Sum(nil) + + sign, err := signer.Sign(hash) + if err != nil { + return nil, fmt.Errorf("signing failed: %w", err) + } + return sign, nil +} + +func SignData(input string, privKey *PrivateKey, wg *sync.WaitGroup) response { + defer wg.Done() + + // Decode the base64 input + message, err := base64.StdEncoding.DecodeString(input) + if err != nil { + log.Fatalf("error decoding base64 input %v", err) + } + + // Generate signature with Falcon signer + signature, err := privKey.SignPQC(message) + if err != nil { + log.Fatalf("error signing message: %v", err) + } + + // Get the public key + pubKey := privKey.PublicKey.Pk + + resp := response{ + Signature: base64.StdEncoding.EncodeToString(signature), + PublicKey: base64.StdEncoding.EncodeToString(pubKey[:]), + } + + return resp + +} diff --git a/post-quantum-server-testing/src/go.mod b/post-quantum-server-testing/src/go.mod new file mode 100644 index 0000000..f97c356 --- /dev/null +++ b/post-quantum-server-testing/src/go.mod @@ -0,0 +1,40 @@ +module server-testing + +go 1.24.0 + +require ( + cloud.google.com/go v0.120.0 // indirect + cloud.google.com/go/auth v0.16.4 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.8.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/kms v1.23.2 // indirect + cloud.google.com/go/longrunning v0.6.7 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/open-quantum-safe/liboqs-go v0.0.0-20250119172907-28b5301df438 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/time v0.12.0 // indirect + google.golang.org/api v0.247.0 // indirect + google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect + google.golang.org/grpc v1.74.2 // indirect + google.golang.org/protobuf v1.36.10 // indirect +) diff --git a/post-quantum-server-testing/src/go.sum b/post-quantum-server-testing/src/go.sum new file mode 100644 index 0000000..aaafab0 --- /dev/null +++ b/post-quantum-server-testing/src/go.sum @@ -0,0 +1,71 @@ +cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8= +cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= +cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= +cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/open-quantum-safe/liboqs-go v0.0.0-20250119172907-28b5301df438 h1:rqhyfDxqF50veu/A7HsgRBShVN8Gqz4mmrgtRr6KnLo= +github.com/open-quantum-safe/liboqs-go v0.0.0-20250119172907-28b5301df438/go.mod h1:OoIQ+v4rM6S6cF9zLGxsnsXX9vwv7WLp9s0TV2FbD6M= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= +google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/post-quantum-server-testing/src/main.go b/post-quantum-server-testing/src/main.go new file mode 100644 index 0000000..669f826 --- /dev/null +++ b/post-quantum-server-testing/src/main.go @@ -0,0 +1,229 @@ +package main + +import ( + "fmt" + "log" + "os" + "server-testing/ecdsa_local" + "server-testing/falcon" + "server-testing/mldsa" + "server-testing/rsa" + "strings" + "sync" + "time" + + "github.com/joho/godotenv" +) + +const ( + SMALLINPUT = "aGkK" // "hi" (<1MB) +) + +var ( + MEDIUMINPUT = strings.Repeat("aGkK", 1024*512) // "hi" repeatedly (1-2MB) +) + +// use godot package to load/read the .env file and +// return the value of the key +func goDotEnvVariable(key string) string { + // Ignored error for gcp cloud run + _ = godotenv.Load(".env") + + return os.Getenv(key) +} + +func testMldsaSmallPayload(iterations int, location string, keyRing string, projectID string) { + keyName := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/mldsa/cryptoKeyVersions/1", projectID, location, keyRing) + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = mldsa.SignData(SMALLINPUT, "mldsa", keyName, &wg) + }(i) + } + wg.Wait() + + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Small Payload ML-DSA-65: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func testMldsaMediumPayload(iterations int, location string, keyRing string, projectID string) { + keyName := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/mldsa/cryptoKeyVersions/1", projectID, location, keyRing) + + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = mldsa.SignData(MEDIUMINPUT, "mldsa", keyName, &wg) + }(i) + } + wg.Wait() + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Medium Payload ML-DSA-65: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func testFalconSmallPayload(iterations int, privKey *falcon.PrivateKey) { + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = falcon.SignData(SMALLINPUT, privKey, &wg) + }(i) + } + wg.Wait() + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Small Payload Falcon-512: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func testFalconMediumPayload(iterations int, privKey *falcon.PrivateKey) { + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = falcon.SignData(MEDIUMINPUT, privKey, &wg) + }(i) + } + wg.Wait() + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Medium Payload Falcon-512: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func testEcdsaSmallPayload(iterations int, privKey *ecdsa_local.PrivateKey) { + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = ecdsa_local.SignData(SMALLINPUT, privKey, &wg) + }(i) + } + wg.Wait() + + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Small Payload ECDSA-384: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func testEcdsaMediumPayload(iterations int, privKey *ecdsa_local.PrivateKey) { + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = ecdsa_local.SignData(MEDIUMINPUT, privKey, &wg) + }(i) + } + wg.Wait() + + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Medium Payload ECDSA-384: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func testRsaSmallPayload(iterations int, location string, keyRing string, projectID string) { + keyName := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/rsa/cryptoKeyVersions/1", projectID, location, keyRing) + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = rsa.SignData(SMALLINPUT, "rsa", keyName, &wg) + }(i) + } + wg.Wait() + + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Small Payload RSA-4096: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func testRsaMediumPayload(iterations int, location string, keyRing string, projectID string) { + keyName := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/rsa/cryptoKeyVersions/1", projectID, location, keyRing) + // Get the time pre-signing + start := time.Now() + + // Create a waitgroup + var wg sync.WaitGroup + wg.Add(iterations) + + for i := 0; i < iterations; i++ { + go func(j int) { + _ = rsa.SignData(MEDIUMINPUT, "rsa", keyName, &wg) + }(i) + } + wg.Wait() + + // Get the time post-signing + elapsed := time.Since(start) + fmt.Printf("Medium Payload RSA-4096: %.3f ms\n", (elapsed.Seconds()*1000)/float64(iterations)) +} + +func main() { + iterations := 100 + location := goDotEnvVariable("LOCATION") + keyRing := goDotEnvVariable("KEYRING") + projectID := goDotEnvVariable("PROJECT_ID") + + // Generate a falcon private key + privKeyFalcon, err := falcon.GenerateKey() + if err != nil { + log.Fatalf("Failed to generate a private key: %v", err) + } + + // Generate an ecdsa private key + privKeyEcdsa, err := ecdsa_local.GenerateKey() + if err != nil { + log.Fatalf("Failed to generate a private key: %v", err) + } + + fmt.Printf("---Running Tests: Avg time per signature---\n\n") + + // Run the small payload tests + testFalconSmallPayload(iterations, privKeyFalcon) + testEcdsaSmallPayload(iterations, privKeyEcdsa) + testRsaSmallPayload(iterations, location, keyRing, projectID) + testMldsaSmallPayload(iterations, location, keyRing, projectID) + + // Run the medium payload tests + testFalconMediumPayload(iterations, privKeyFalcon) + testEcdsaMediumPayload(iterations, privKeyEcdsa) + testRsaMediumPayload(iterations, location, keyRing, projectID) + testMldsaMediumPayload(iterations, location, keyRing, projectID) + +} diff --git a/post-quantum-server-testing/src/mldsa/mldsa.go b/post-quantum-server-testing/src/mldsa/mldsa.go new file mode 100644 index 0000000..b6e4c83 --- /dev/null +++ b/post-quantum-server-testing/src/mldsa/mldsa.go @@ -0,0 +1,114 @@ +package mldsa + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "fmt" + "hash/crc32" + "log" + "sync" + + kms "cloud.google.com/go/kms/apiv1" + kmspb "cloud.google.com/go/kms/apiv1/kmspb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// This is a struct for the request message +type request struct { + Input string `json:"input"` + KeyID string `json:"keyid"` +} + +// This function processes the request struct and returns the byte message +func parseInput(req request) ([]byte, error) { + if req.Input == "" { + return nil, fmt.Errorf("missing the json data input") + } + if req.KeyID == "" { + return nil, fmt.Errorf("missing the json keyid") + } + + message, err := base64.StdEncoding.DecodeString(req.Input) + if err != nil { + return nil, fmt.Errorf("error decoding base64 input %v", err) + } + + // This returns the json []byte + return message, nil +} + +// signAsymmetric will sign a plaintext message using a saved asymmetric private +// key stored in Cloud KMS. +func signAsymmetric(name string, message []byte) ([]byte, error) { + // Create the client. + ctx := context.Background() + client, err := kms.NewKeyManagementClient(ctx) + if err != nil { + log.Fatalf("failed to setup client: %v", err) + } + defer client.Close() + + // hash the message with sha256 + h := sha256.New() + h.Write(message) + bs := h.Sum(nil) + + plaintext := bs[:] + + // Compute data CRC32C. + crc32c := func(data []byte) uint32 { + t := crc32.MakeTable(crc32.Castagnoli) + return crc32.Checksum(data, t) + + } + + checksum := crc32c(plaintext) + + // Build the signing request. + req := &kmspb.AsymmetricSignRequest{ + Name: name, + Data: plaintext, + DataCrc32C: wrapperspb.Int64(int64(checksum)), + } + + // Call the API. + result, err := client.AsymmetricSign(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to sign digest: %w", err) + } + + // Perform integrity verification on result. + if result.Name != req.Name { + return nil, fmt.Errorf("AsymmetricSign: request corrupted in-transit") + } + if int64(crc32c(result.Signature)) != result.SignatureCrc32C.Value { + return nil, fmt.Errorf("AsymmetricSign: response corrupted in-transit") + } + + return result.Signature, nil +} + +func SignData(input string, key string, keyName string, wg *sync.WaitGroup) string { + defer wg.Done() + + var reqData request + + // Input request data to be parsed + reqData.Input = input + reqData.KeyID = key + + // Process the input from the request data + message, err := parseInput(reqData) + if err != nil { + log.Fatalf("Error parsing the input request data %v", err) + } + + // Do the asymmetric signing + signature, err := signAsymmetric(keyName, message) + if err != nil { + log.Fatalf("Error signing the message: %v", err) + } + + return base64.StdEncoding.EncodeToString(signature) +} diff --git a/post-quantum-server-testing/src/rsa/rsa.go b/post-quantum-server-testing/src/rsa/rsa.go new file mode 100644 index 0000000..5e46c0a --- /dev/null +++ b/post-quantum-server-testing/src/rsa/rsa.go @@ -0,0 +1,123 @@ +package rsa + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "fmt" + "hash/crc32" + "log" + "sync" + + kms "cloud.google.com/go/kms/apiv1" + kmspb "cloud.google.com/go/kms/apiv1/kmspb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// This is a struct for the request message +type request struct { + Input string `json:"input"` + KeyID string `json:"keyid"` +} + +// This function processes the request struct and returns a struct of the data +func parseInput(req request) ([]byte, error) { + if req.Input == "" { + return nil, fmt.Errorf("missing the json data input") + } + if req.KeyID == "" { + return nil, fmt.Errorf("missing the json keyid") + } + + message, err := base64.StdEncoding.DecodeString(req.Input) + if err != nil { + return nil, fmt.Errorf("error decoding base64 input %v", err) + } + + // This returns the json []byte + return message, nil +} + +// signAsymmetric will sign a plaintext message using a saved asymmetric private +// key stored in Cloud KMS. +func signAsymmetric(name string, message []byte) ([]byte, error) { + // Create the client. + ctx := context.Background() + client, err := kms.NewKeyManagementClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to create kms client: %w", err) + } + defer client.Close() + + // ciphertexts are always byte arrays. + plaintext := message + + // Calculate the digest of the message. + digest := sha256.New() + if _, err := digest.Write(plaintext); err != nil { + return nil, fmt.Errorf("failed to create digest: %w", err) + } + + // Compute digest's CRC32C. + crc32c := func(data []byte) uint32 { + t := crc32.MakeTable(crc32.Castagnoli) + return crc32.Checksum(data, t) + + } + digestCRC32C := crc32c(digest.Sum(nil)) + + // Build the signing request. + req := &kmspb.AsymmetricSignRequest{ + Name: name, + Digest: &kmspb.Digest{ + Digest: &kmspb.Digest_Sha256{ + Sha256: digest.Sum(nil), + }, + }, + DigestCrc32C: wrapperspb.Int64(int64(digestCRC32C)), + } + + // Call the API. + result, err := client.AsymmetricSign(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to sign digest: %w", err) + } + + // Perform integrity verification on result. + if !result.VerifiedDigestCrc32C { + return nil, fmt.Errorf("AsymmetricSign: request corrupted in-transit") + } + if result.Name != req.Name { + return nil, fmt.Errorf("AsymmetricSign: request corrupted in-transit") + } + if int64(crc32c(result.Signature)) != result.SignatureCrc32C.Value { + return nil, fmt.Errorf("AsymmetricSign: response corrupted in-transit") + } + + return result.Signature, nil +} + +// This function signs the data and and returns the signature as a base64 string +func SignData(input string, key string, keyName string, wg *sync.WaitGroup) string { + defer wg.Done() + + var reqData request + + //hard coded the request data + reqData.Input = input + reqData.KeyID = key + + // Process the input from the request data + message, err := parseInput(reqData) + if err != nil { + log.Fatalf("Error parsing the input request data %v", err) + } + + // Do the asymmetric signing + signature, err := signAsymmetric(keyName, message) + if err != nil { + log.Fatalf("Error signing the message: %v", err) + } + + return base64.StdEncoding.EncodeToString(signature) +}