diff --git a/Dockerfile-kms-importer b/Dockerfile-kms-importer index 5e126cbc2..b728f6aa3 100644 --- a/Dockerfile-kms-importer +++ b/Dockerfile-kms-importer @@ -1,28 +1,23 @@ FROM golang:1.22 AS base ARG VERSION -ARG ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY -ARG ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY -ARG ISSUER_KMS_ETH_PROVIDER_AWS_REGION - WORKDIR /service ENV GOBIN /service/bin COPY go.mod . COPY go.sum . COPY ./cmd/kms_priv_key_importer/ ./cmd/kms_priv_key_importer/ -COPY ./internal ./internal - +COPY ./internal/config ./internal/config +COPY ./internal/kms ./internal/kms +COPY ./internal/log ./internal/log +COPY ./internal/providers ./internal/providers +COPY ./internal/common ./internal/common +COPY ./pkg/PKCS8DER ./pkg/PKCS8DER - -RUN go install -buildvcs=false -ldflags "-X main.build=${VERSION}" ./cmd/... +RUN go install -buildvcs=false -ldflags "-X main.build=${VERSION}" ./cmd/kms_priv_key_importer/main.go FROM alpine:latest -ARG ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY -ARG ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY -ARG ISSUER_KMS_ETH_PROVIDER_AWS_REGION - RUN apk add --no-cache libstdc++ gcompat libgomp RUN apk add --update busybox>1.3.1-r0 RUN apk add --update openssl>3.1.4-r1 @@ -35,22 +30,5 @@ RUN apk add doas; \ echo 'permit nopass :wheel as root' >> /etc/doas.d/doas.conf; RUN chmod g+rx,o+rx / -COPY --from=base ./service/bin/* ./ -COPY --from=base ./service/cmd/kms_priv_key_importer/aws_kms_material_key_importer.sh ./aws_kms_material_key_importer.sh -RUN chmod +x ./aws_kms_material_key_importer.sh - -RUN if [ -n "$ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY" ]; then \ - aws configure set aws_access_key_id ${ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY} --profile privadoid; \ - else \ - echo "ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY is not set"; \ - fi -RUN if [ -n "$ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY" ]; then \ - aws configure set aws_secret_access_key ${ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY} --profile privadoid; \ - else \ - echo "ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY is not set"; \ - fi -RUN if [ -n "$ISSUER_KMS_ETH_PROVIDER_AWS_REGION" ]; then \ - aws configure set region ${ISSUER_KMS_ETH_PROVIDER_AWS_REGION} --profile privadoid; \ - else \ - echo "ISSUER_KMS_ETH_PROVIDER_AWS_REGION is not set"; \ - fi +COPY --from=base ./service/bin/main /usr/local/bin/kms_priv_key_importer +RUN chmod +x /usr/local/bin/kms_priv_key_importer \ No newline at end of file diff --git a/Makefile b/Makefile index d5adc9dbb..cd1e55e0b 100644 --- a/Makefile +++ b/Makefile @@ -214,46 +214,20 @@ lint-fix: $(BIN)/golangci-lint import-private-key-to-kms: ifeq ($(ISSUER_KMS_ETH_PROVIDER), aws-kms) @echo ">>> importing private key to AWS KMS" - docker build --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY=$(aws_access_key) \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY=$(aws_secret_key) \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_REGION=$(aws_region) \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_URL=$(aws_endpoint) -t privadoid-kms-importer -f ./Dockerfile-kms-importer . - $(eval result = $(shell docker run -it -v ./.env-issuer:/.env-issuer \ - --network issuer-network \ - privadoid-kms-importer ./kms_priv_key_importer)) - @echo "result: $(result)" - $(eval keyID = $(shell echo $(result) | grep "key created keyId=" | sed 's/.*keyId=//')) - @if [ -n "$(keyID)" ]; then \ - docker run -it --rm -v ./.env-issuer:/.env-issuer --network issuer-network \ - privadoid-kms-importer sh ./aws_kms_material_key_importer.sh $(private_key) $(keyID) privadoid $(aws_endpoint) ; \ - else \ - echo "something went wrong because keyID is empty"; \ - fi + @go build -o kms_priv_key_importer cmd/kms_priv_key_importer/main.go + ./kms_priv_key_importer --privateKey=$(private_key) else ifeq ($(ISSUER_KMS_ETH_PROVIDER), aws-sm) @echo ">>> importing private key to AWS Secrets Manager" - docker build --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY=$(aws_access_key) \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY=$(aws_secret_key) \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_REGION=$(aws_region) \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_URL=$(aws_endpoint) -t privadoid-kms-importer -f ./Dockerfile-kms-importer . - $(eval result=$(shell docker run -it -v ./.env-issuer:/.env-issuer \ - --network issuer-network \ - privadoid-kms-importer ./kms_priv_key_importer --privateKey=$(private_key))) - @echo "$(result)" + @go build -o kms_priv_key_importer cmd/kms_priv_key_importer/main.go + ./kms_priv_key_importer --privateKey=$(private_key) else ifeq ($(ISSUER_KMS_ETH_PROVIDER), localstorage) echo ">>> importing private key to LOCALSTORAGE" - @docker build -t privadoid-kms-importer -f ./Dockerfile-kms-importer . - @if [ ! -f "$(ISSUER_KMS_PROVIDER_LOCAL_STORAGE_FILE_PATH)/kms_localstorage_keys.json" ]; then \ - mkdir -p $(ISSUER_KMS_PROVIDER_LOCAL_STORAGE_FILE_PATH); \ - touch $(ISSUER_KMS_PROVIDER_LOCAL_STORAGE_FILE_PATH)/kms_localstorage_keys.json; \ - echo "[]" > $(ISSUER_KMS_PROVIDER_LOCAL_STORAGE_FILE_PATH)/kms_localstorage_keys.json; \ - fi - docker run --rm -it -v ./.env-issuer:/.env-issuer -v $(ISSUER_KMS_PROVIDER_LOCAL_STORAGE_FILE_PATH)/kms_localstorage_keys.json:/localstoragekeys/kms_localstorage_keys.json \ - privadoid-kms-importer ./kms_priv_key_importer --privateKey=$(private_key) + @go build -o kms_priv_key_importer cmd/kms_priv_key_importer/main.go + ./kms_priv_key_importer --privateKey=$(private_key) else ifeq ($(ISSUER_KMS_ETH_PROVIDER), vault) - @echo ">>> importing private key to VAULT" - @docker build -t privadoid-kms-importer -f ./Dockerfile-kms-importer . - docker run --rm -it -v ./.env-issuer:/.env-issuer --network issuer-network \ - privadoid-kms-importer ./kms_priv_key_importer --privateKey=$(private_key) + echo ">>> importing private key to Vault" + @go build -o kms_priv_key_importer cmd/kms_priv_key_importer/main.go + ./kms_priv_key_importer --privateKey=$(private_key) else @echo "ISSUER_KMS_ETH_PROVIDER is not set" endif diff --git a/cmd/kms_priv_key_importer/main.go b/cmd/kms_priv_key_importer/main.go index ca9629d8d..2b0215a77 100644 --- a/cmd/kms_priv_key_importer/main.go +++ b/cmd/kms_priv_key_importer/main.go @@ -2,7 +2,12 @@ package main import ( "context" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" "encoding/base64" + "encoding/hex" "encoding/json" "errors" "flag" @@ -20,6 +25,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/kms/types" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" vault "github.com/hashicorp/vault/api" "github.com/joho/godotenv" @@ -27,6 +33,7 @@ import ( "github.com/polygonid/sh-id-platform/internal/kms" "github.com/polygonid/sh-id-platform/internal/log" "github.com/polygonid/sh-id-platform/internal/providers" + "github.com/polygonid/sh-id-platform/pkg/PKCS8DER" ) const ( @@ -90,7 +97,8 @@ func main() { material[jsonKeyPath] = issuerPublishKeyPathVar material[jsonKeyType] = ethereum - if issuerKMSETHProviderToUse == config.LocalStorage { + switch issuerKMSETHProviderToUse { + case config.LocalStorage: material[jsonPrivateKey] = *fPrivateKey if err := saveKeyMaterialToFile(ctx, issuerKmsPluginLocalStorageFilePath, kms.LocalStorageFileName, material); err != nil { log.Error(ctx, "cannot save key material to file", "err", err) @@ -98,9 +106,8 @@ func main() { } log.Info(ctx, "private key saved to file:", "path:", kms.LocalStorageFileName) return - } - if issuerKMSETHProviderToUse == config.Vault { + case config.Vault: var vaultCli *vault.Client var vaultErr error vaultTokenVar := os.Getenv(issuerKeyStoreToken) @@ -150,9 +157,8 @@ func main() { log.Info(ctx, "private key saved to vault:", "path:", issuerPublishKeyPathVar) return - } - if issuerKMSETHProviderToUse == config.AWSSM { + case config.AWSSM: awsAccessKey := os.Getenv(awsAccessKey) awsSecretKey := os.Getenv(awsSecretKey) awsRegion := os.Getenv(awsRegion) @@ -200,9 +206,8 @@ func main() { } log.Info(ctx, "private key saved to aws:", "path:", issuerPublishKeyPathVar) return - } - if issuerKMSETHProviderToUse == config.AWSKMS { + case config.AWSKMS: awsAccessKey := os.Getenv(awsAccessKey) awsSecretKey := os.Getenv(awsSecretKey) awsRegion := os.Getenv(awsRegion) @@ -213,13 +218,17 @@ func main() { return } - keyId, err := createEmptyKey(ctx, awsAccessKey, awsSecretKey, awsRegion, awsURLEndpoint, issuerPublishKeyPathVar) + keyId, err := createAWSKMSKey(ctx, *fPrivateKey, awsAccessKey, awsSecretKey, awsRegion, awsURLEndpoint, issuerPublishKeyPathVar) if err != nil { log.Error(ctx, "cannot create empty key", "err", err) return } log.Info(ctx, "key created", "keyId", *keyId) return + + default: + log.Error(ctx, "kms eth provider is invalid, supported values are: localstorage, vault, aws-sm and aws-kms") + return } } @@ -253,9 +262,13 @@ func validate(issuerKMSETHProviderToUse string, fPrivateKey *string, ctx context return nil } +// createAWSKMSKey creates a new AWS KMS key with the provided private key and alias. +// It imports the private key material into the KMS key and creates an alias for it. // //nolint:unused -func createEmptyKey(ctx context.Context, awsAccessKey, awsSecretKey, awsRegion string, awsURL string, privateKeyAlias string) (*string, error) { +func createAWSKMSKey(ctx context.Context, privateKey string, awsAccessKey, awsSecretKey, awsRegion string, awsURL string, privateKeyAlias string) (*string, error) { + alias := "alias/" + privateKeyAlias + cfg, err := awsconfig.LoadDefaultConfig( ctx, awsconfig.WithRegion(awsRegion), @@ -275,6 +288,36 @@ func createEmptyKey(ctx context.Context, awsAccessKey, awsSecretKey, awsRegion s } svc := awskms.NewFromConfig(cfg, options...) + + // Check if alias exists + listAliasesInput := &awskms.ListAliasesInput{} + aliases, err := svc.ListAliases(ctx, listAliasesInput) + if err != nil { + log.Error(ctx, "cannot list aliases", "err", err) + return nil, err + } + for _, a := range aliases.Aliases { + if a.AliasName != nil && *a.AliasName == alias { + return nil, fmt.Errorf("alias %s already exists", alias) + } + } + + privBytes, err := hex.DecodeString(privateKey) + if err != nil { + return nil, fmt.Errorf("error decoding private key: %w", err) + } + + privKey, err := crypto.ToECDSA(privBytes) + if err != nil { + return nil, fmt.Errorf("error converting private key to ECDSA: %w", err) + } + + privKey.Curve = secp256k1.S256() + der, err := PKCS8DER.MarshalECPrivateKeyToPKCS8DER(privKey) + if err != nil { + return nil, fmt.Errorf("error marshaling private key to PKCS8 DER: %w", err) + } + input := &awskms.CreateKeyInput{ KeySpec: types.KeySpecEccSecgP256k1, KeyUsage: types.KeyUsageTypeSignVerify, @@ -282,16 +325,46 @@ func createEmptyKey(ctx context.Context, awsAccessKey, awsSecretKey, awsRegion s Description: aws.String("imported key"), } - result, err := svc.CreateKey(ctx, input) + createOutput, err := svc.CreateKey(ctx, input) if err != nil { log.Error(ctx, "cannot create key", "err", err) return nil, err } + keyID := *createOutput.KeyMetadata.KeyId + params, err := svc.GetParametersForImport(ctx, &awskms.GetParametersForImportInput{ + KeyId: aws.String(keyID), + WrappingAlgorithm: types.AlgorithmSpecRsaesOaepSha256, + WrappingKeySpec: types.WrappingKeySpecRsa2048, + }) + if err != nil { + log.Error(ctx, "cannot get parameters for import", "err", err) + return nil, err + } + + rsaPubKey, err := x509.ParsePKIXPublicKey(params.PublicKey) + if err != nil { + log.Error(ctx, "cannot parse public key", "err", err) + return nil, err + } + encryptedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPubKey.(*rsa.PublicKey), der, nil) + if err != nil { + log.Error(ctx, "cannot encrypt key material", "err", err) + return nil, err + } + _, err = svc.ImportKeyMaterial(ctx, &awskms.ImportKeyMaterialInput{ + KeyId: aws.String(keyID), + ImportToken: params.ImportToken, + EncryptedKeyMaterial: encryptedKey, + ExpirationModel: types.ExpirationModelTypeKeyMaterialDoesNotExpire, + }) + if err != nil { + log.Error(ctx, "cannot import key material", "err", err) + return nil, err + } - alias := "alias/" + privateKeyAlias inputAlias := &awskms.CreateAliasInput{ AliasName: aws.String(alias), - TargetKeyId: result.KeyMetadata.Arn, + TargetKeyId: createOutput.KeyMetadata.Arn, } _, err = svc.CreateAlias(ctx, inputAlias) @@ -300,7 +373,7 @@ func createEmptyKey(ctx context.Context, awsAccessKey, awsSecretKey, awsRegion s } log.Info(ctx, "alias created:", "alias:", alias) - return result.KeyMetadata.KeyId, nil + return createOutput.KeyMetadata.KeyId, nil } func saveKeyMaterialToFile(ctx context.Context, folderPath, file string, keyMaterial map[string]string) error { diff --git a/cmd/kms_priv_key_importer/readme.md b/cmd/kms_priv_key_importer/readme.md index 81b43d0d0..07dc05997 100644 --- a/cmd/kms_priv_key_importer/readme.md +++ b/cmd/kms_priv_key_importer/readme.md @@ -1,8 +1,8 @@ ### Requirements You have to have installed the following tools: - [Go](https://golang.org/doc/install) -- [aws cli](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) -- if you want to import your private key to AWS KMS or Secrets Manager -- [openssl](https://www.openssl.org/) -- if you want to import your private key to AWS KMS +- [Docker](https://docs.docker.com/get-docker/) + this tools needs the following environment variables to be set up: ``` @@ -42,13 +42,6 @@ or you can build the binary and run it: $ go build -o kms_priv_key_importer cmd/kms_priv_key_importer/main.go ``` -and then run: - -```shell - ./kms_priv_key_importer --privateKey -``` - - ### How import your private key to AWS KMS First you need to create a new key in AWS KMS, so export the variables defined in the requirements section: ```shell @@ -61,36 +54,16 @@ export ISSUER_KMS_AWS_URL= # optional and run the following command: ```shell -$ go run . -``` -To get the key id you have to take a look at the output (or logs) of the previous command, it will be something like: - -```logs -2024/07/10 10:38:14 INFO alias created: alias:=alias/pbkey -2024/07/10 10:38:14 INFO key created keyId=157a8b2a-e5e9-4414-b9c5-301ce828f6c5 -``` - -then you can import your private key using the following command: - -```shell -$ chmod +x aws_kms_material_key_imporer.sh -$ ./kms_priv_key_importer [] +$ ./kms_priv_key_importer --privateKey ``` -where: -* `privateETHKey` is your private key in hex format (`d3bdf6f80e510b2efed2d1dd2652f3ad5d433b8eeff0cb622d426d259576b551`) -* `key-id` is the key id of the key created in AWS KMS (in this example `157a8b2a-e5e9-4414-b9c5-301ce828f6c5`) -* `aws-profile` is the profile name in your `~/.aws/credentials` file -* `aws-region` is the region where the key was created -* `aws-endpoint-url` is the endpoint url of the AWS KMS service (optional) - -if you get `Key material successfully imported!!!` message, then your private key was successfully imported to AWS KMS. +if you get `key created keyId=` message, then your private key was successfully imported to AWS KMS. ### How import your private key to AWS Secrets Manager Export the variables defined in the requirements section: ```shell -export ISSUER_KMS_ETH_PROVIDER=aws-kms +export ISSUER_KMS_ETH_PROVIDER=aws-sm export ISSUER_KMS_AWS_ACCESS_KEY= export ISSUER_KMS_AWS_SECRET_KEY= export ISSUER_KMS_AWS_REGION= @@ -99,66 +72,60 @@ export ISSUER_KMS_AWS_URL= # optional and run the following command: ```shell -$ go run . --privateKey +$ ./kms_priv_key_importer --privateKey ``` that's it, your private key was successfully imported to AWS Secrets Manager. -### Running Importer with Docker (AWS KMS) -In the root project folder run: - +### Docker Alternative Sample (localstorage) 📂 +First you need to build the docker image: ```shell -docker build --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY= \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY= \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_REGION= --build-arg= -t privadoid-kms-importer -f ./Dockerfile-kms-importer . +$ docker build -t kms-priv-key-importer -f ./cmd/kms_priv_key_importer ``` - -Note: if you don't have an endpoint url, you can remove the `--build-arg=` argument from the command above. - -After the docker image is created run the following command (make sure you have the .env-issuer with your env vars): - +Then you can run the docker container with the following command (at the root of the project): ```shell -docker run -it -v ./.env-issuer:/.env-issuer privadoid-kms-importer sh -``` - -inside the container `privadoid-kms-importer` execute: - -``` -./kms_priv_key_importer -``` - -you will see something like this: - +$ mkdir localstoragekeys + +$ docker run --rm -it \ +-e ISSUER_PUBLISH_KEY_PATH=pbkey \ +-e ISSUER_KMS_ETH_PROVIDER=localstorage \ +-e ISSUER_KMS_PROVIDER_LOCAL_STORAGE_FILE_PATH=localstoragekeys \ +-v $(pwd)/localstoragekeys:/localstoragekeys \ +kms-priv-key-importer kms_priv_key_importer --privateKey +``` + +### Docker Alternative Sample (AWS-KMS) 🐳 +First you need to build the docker image: ```shell -2024/07/10 15:27:54 INFO alias created: alias:=alias/pbkey -2024/07/10 15:27:54 INFO key created keyId=9bb5b78b-c288-44a7-b1d4-0543e0a6 +$ docker build -t kms-priv-key-importer -f ./cmd/kms_priv_key_importer ``` -then import the material key - +Then you can run the docker container with the following command: ```shell -sh ./aws_kms_material_key_importer.sh privadoid [] -``` -if you get `Key material successfully imported!!!` message, then your private key was successfully imported to AWS KMS. - - -### Running Importer with Docker (AWS Secrets Manager) -In the root project folder run: - -```shell -docker build --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_ACCESS_KEY= \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_SECRET_KEY= \ - --build-arg ISSUER_KMS_ETH_PROVIDER_AWS_REGION= --build-arg= -t privadoid-kms-importer -f ./Dockerfile-kms-importer . -``` - -after the docker image is created run the following command (make sure you have the .env-issuer with your env vars): - +$ docker run --rm -it \ +-e ISSUER_PUBLISH_KEY_PATH=pbkey \ +-e ISSUER_KMS_ETH_PROVIDER=aws-kms \ +-e ISSUER_KMS_AWS_REGION= \ # "local" if you are using localstack +-e ISSUER_KMS_AWS_ACCESS_KEY= \ +-e ISSUER_KMS_AWS_SECRET_KEY= \ +# -e ISSUER_KMS_AWS_URL=http://host.docker.internal:4566 \ # optional, use it if you are using localstack +kms-priv-key-importer kms_priv_key_importer --privateKey +``` + +### Docker Alternative Sample (AWS-SM) 🐳 +First you need to build the docker image: ```shell -docker run -it -v ./.env-issuer:/.env-issuer privadoid-kms-importer sh +$ docker build -t kms-priv-key-importer -f ./cmd/kms_priv_key_importer ``` -inside the container `privadoid-kms-importer` execute: - +Then you can run the docker container with the following command: ```shell -./kms_priv_key_importer --privateKey +$ docker run --rm -it \ +-e ISSUER_PUBLISH_KEY_PATH=pbkey \ +-e ISSUER_KMS_ETH_PROVIDER=aws-sm \ +-e ISSUER_KMS_AWS_REGION= \ # "local" if you are using localstack +-e ISSUER_KMS_AWS_ACCESS_KEY= \ +-e ISSUER_KMS_AWS_SECRET_KEY= \ +# -e ISSUER_KMS_AWS_URL=http://host.docker.internal:4566 \ # optional, use it if you are using localstack +kms-priv-key-importer kms_priv_key_importer --privateKey ``` \ No newline at end of file diff --git a/internal/api/links_test.go b/internal/api/links_test.go index 0c7a58bfd..9b968e858 100644 --- a/internal/api/links_test.go +++ b/internal/api/links_test.go @@ -45,6 +45,8 @@ func TestServer_CreateLink(t *testing.T) { handler := getHandler(ctx, server) + expiration := time.Now().Add(time.Hour) + type expected struct { response CreateLinkResponseObject httpCode int @@ -69,7 +71,7 @@ func TestServer_CreateLink(t *testing.T) { auth: authOk, body: CreateLinkRequest{ SchemaID: importedSchema.ID, - Expiration: common.ToPointer(time.Date(2030, 8, 15, 14, 30, 45, 100, time.Local)), + Expiration: common.ToPointer(expiration), CredentialExpiration: common.ToPointer(time.Date(2023, 8, 15, 14, 30, 45, 100, time.Local)), LimitedClaims: common.ToPointer(10), CredentialSubject: CredentialSubject{"birthday": 19790911, "documentType": 12}, @@ -86,7 +88,7 @@ func TestServer_CreateLink(t *testing.T) { auth: authOk, body: CreateLinkRequest{ SchemaID: importedSchema.ID, - Expiration: common.ToPointer(time.Date(2030, 8, 15, 14, 30, 45, 100, time.Local)), + Expiration: common.ToPointer(expiration), CredentialExpiration: common.ToPointer(time.Date(2023, 8, 15, 14, 30, 45, 100, time.Local)), LimitedClaims: common.ToPointer(10), CredentialSubject: CredentialSubject{"birthday": 19790911, "documentType": 12}, @@ -120,7 +122,7 @@ func TestServer_CreateLink(t *testing.T) { auth: authOk, body: CreateLinkRequest{ SchemaID: importedSchema.ID, - Expiration: common.ToPointer(time.Date(2030, 8, 15, 14, 30, 45, 100, time.Local)), + Expiration: common.ToPointer(expiration), CredentialExpiration: nil, LimitedClaims: common.ToPointer(10), CredentialSubject: CredentialSubject{"birthday": 19790911, "documentType": 12}, @@ -154,7 +156,7 @@ func TestServer_CreateLink(t *testing.T) { auth: authOk, body: CreateLinkRequest{ SchemaID: importedSchema.ID, - Expiration: common.ToPointer(time.Date(2030, 8, 15, 14, 30, 45, 100, time.Local)), + Expiration: common.ToPointer(expiration), CredentialExpiration: common.ToPointer(time.Date(2020, 8, 15, 14, 30, 45, 100, time.Local)), LimitedClaims: common.ToPointer(10), CredentialSubject: CredentialSubject{}, @@ -171,7 +173,7 @@ func TestServer_CreateLink(t *testing.T) { auth: authOk, body: CreateLinkRequest{ SchemaID: importedSchema.ID, - Expiration: common.ToPointer(time.Date(2030, 8, 15, 14, 30, 45, 100, time.Local)), + Expiration: common.ToPointer(expiration), CredentialExpiration: common.ToPointer(time.Date(2000, 8, 15, 14, 30, 45, 100, time.Local)), LimitedClaims: common.ToPointer(10), CredentialSubject: CredentialSubject{"birthday": 19790911, "documentType": true}, @@ -188,7 +190,7 @@ func TestServer_CreateLink(t *testing.T) { auth: authOk, body: CreateLinkRequest{ SchemaID: uuid.New(), - Expiration: common.ToPointer(time.Date(2030, 8, 15, 14, 30, 45, 100, time.Local)), + Expiration: common.ToPointer(expiration), CredentialExpiration: common.ToPointer(time.Date(2000, 8, 15, 14, 30, 45, 100, time.Local)), LimitedClaims: common.ToPointer(10), CredentialSubject: CredentialSubject{"birthday": 19790911, "documentType": 12}, diff --git a/pkg/PKCS8DER/helper.go b/pkg/PKCS8DER/helper.go new file mode 100644 index 000000000..6e0ed5035 --- /dev/null +++ b/pkg/PKCS8DER/helper.go @@ -0,0 +1,81 @@ +package PKCS8DER + +import ( + "crypto/ecdsa" + "encoding/asn1" + "errors" + + "github.com/ethereum/go-ethereum/crypto/secp256k1" +) + +var ( + oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + oidNamedCurveS256 = asn1.ObjectIdentifier{1, 3, 132, 0, 10} +) + +const ( + bitLengthMultiplier = 8 // Number of bits in a byte +) + +type pkcs8 struct { + Version int + Algo pkixAlgorithmIdentifier + PrivateKey []byte +} + +type pkixAlgorithmIdentifier struct { + Algorithm asn1.ObjectIdentifier + Parameters asn1.ObjectIdentifier +} + +type ecPrivateKey struct { + Version int + PrivateKey []byte + NamedCurveOID asn1.ObjectIdentifier `asn1:"explicit,tag:0"` + PublicKey asn1.BitString `asn1:"explicit,tag:1"` +} + +// MarshalECPrivateKeyToPKCS8DER serializes an ECDSA private key using the secp256k1 curve +// into an unencrypted PKCS#8 DER-encoded byte slice. +// +// This function is necessary because the Go standard library's x509.MarshalPKCS8PrivateKey +// does not support the secp256k1 curve, which is used in Ethereum and other blockchain applications. +// AWS KMS expects imported asymmetric private keys to be in unencrypted PKCS#8 format, +// including an AlgorithmIdentifier with the secp256k1 OID (1.3.132.0.10). +// +// This custom implementation ensures compatibility with AWS KMS by generating +// a valid ASN.1 structure that includes the ECPrivateKey (RFC 5915) nested within +// the PKCS#8 wrapper, using the correct curve identifiers. +func MarshalECPrivateKeyToPKCS8DER(priv *ecdsa.PrivateKey) ([]byte, error) { + if priv == nil { + return nil, errors.New("nil private key") + } + if priv.Curve != secp256k1.S256() { + return nil, errors.New("unsupported curve: only secp256k1 is supported") + } + + pubKeyBytes := append([]byte{0x04}, priv.X.Bytes()...) + pubKeyBytes = append(pubKeyBytes, priv.Y.Bytes()...) + + ecKey := ecPrivateKey{ + Version: 1, + PrivateKey: priv.D.Bytes(), + NamedCurveOID: oidNamedCurveS256, + PublicKey: asn1.BitString{Bytes: pubKeyBytes, BitLength: len(pubKeyBytes) * bitLengthMultiplier}, + } + ecKeyDer, err := asn1.Marshal(ecKey) + if err != nil { + return nil, err + } + + pkcs8Key := pkcs8{ + Version: 0, + Algo: pkixAlgorithmIdentifier{ + Algorithm: oidPublicKeyECDSA, + Parameters: oidNamedCurveS256, + }, + PrivateKey: ecKeyDer, + } + + return asn1.Marshal(pkcs8Key) +}