Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
fe13e8dff6 | |||
10d070dd10 | |||
af826ff457 | |||
90bfc25975 | |||
dadb907740 | |||
bfb5ee44ce | |||
87eac3e11b | |||
60cc3f4073 | |||
d952775922 | |||
2c822aeade | |||
aea5c7619f | |||
dc90ed4488 | |||
b20b1719bd |
159
.drone.yml
159
.drone.yml
@ -1,89 +1,70 @@
|
|||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: cleanup-before
|
name: build-linux
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOOS: linux
|
||||||
|
GOOPTIONS: -mod=vendor
|
||||||
|
SRCFILES: cmd/pki/*.go
|
||||||
|
PROJECTNAME: pki
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: clean
|
- name: build-linux-amd64
|
||||||
image: alpine
|
|
||||||
commands:
|
|
||||||
- rm -rf /build/*
|
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
path: /build
|
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
host:
|
|
||||||
path: /tmp/pki/build
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: default-linux-amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- ./ci-build.sh build
|
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
||||||
environment:
|
environment:
|
||||||
GOOS: linux
|
|
||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
volumes:
|
when:
|
||||||
- name: build
|
event:
|
||||||
path: /build
|
exclude:
|
||||||
|
- tag
|
||||||
volumes:
|
- name: build-linux-arm64
|
||||||
- name: build
|
|
||||||
host:
|
|
||||||
path: /tmp/pki/build
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- cleanup-before
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: default-linux-arm64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- ./ci-build.sh build
|
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
||||||
environment:
|
environment:
|
||||||
GOOS: linux
|
|
||||||
GOARCH: arm64
|
GOARCH: arm64
|
||||||
volumes:
|
when:
|
||||||
- name: build
|
event:
|
||||||
path: /build
|
exclude:
|
||||||
|
- tag
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
host:
|
|
||||||
path: /tmp/pki/build
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- cleanup-before
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: gitea-release
|
name: gitea-release-linux
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOOS: linux
|
||||||
|
GOOPTIONS: -mod=vendor
|
||||||
|
SRCFILES: cmd/pki/*.go
|
||||||
|
PROJECTNAME: pki
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: move
|
- name: build-linux-amd64
|
||||||
image: alpine
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- mv build/* ./
|
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
||||||
volumes:
|
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
|
||||||
- name: build
|
- echo $PROJECTNAME $DRONE_TAG > VERSION
|
||||||
path: /drone/src/build
|
environment:
|
||||||
|
GOARCH: amd64
|
||||||
when:
|
when:
|
||||||
event: tag
|
event:
|
||||||
|
- tag
|
||||||
|
- name: build-linux-arm64
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
||||||
|
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
|
||||||
|
- echo $PROJECTNAME $DRONE_TAG > VERSION
|
||||||
|
environment:
|
||||||
|
GOARCH: arm64
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
- name: release
|
- name: release
|
||||||
image: plugins/gitea-release
|
image: plugins/gitea-release
|
||||||
settings:
|
settings:
|
||||||
@ -95,50 +76,6 @@ steps:
|
|||||||
- sha256
|
- sha256
|
||||||
- sha512
|
- sha512
|
||||||
title: VERSION
|
title: VERSION
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
path: /drone/src/build
|
|
||||||
when:
|
when:
|
||||||
event: tag
|
event:
|
||||||
- name: ls
|
- tag
|
||||||
image: alpine
|
|
||||||
commands:
|
|
||||||
- find .
|
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
path: /drone/src/build
|
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
host:
|
|
||||||
path: /tmp/pki/build
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- default-linux-amd64
|
|
||||||
- default-linux-arm64
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: cleanup-after
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: clean
|
|
||||||
image: alpine
|
|
||||||
commands:
|
|
||||||
- rm -rf /build/*
|
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
path: /build
|
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: build
|
|
||||||
host:
|
|
||||||
path: /tmp/pki/build
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- gitea-release
|
|
||||||
|
18
Makefile
18
Makefile
@ -1,18 +0,0 @@
|
|||||||
# pki Makefile
|
|
||||||
|
|
||||||
GOCMD=go
|
|
||||||
GOBUILDCMD=${GOCMD} build
|
|
||||||
GOOPTIONS=-mod=vendor -ldflags="-s -w"
|
|
||||||
|
|
||||||
RMCMD=rm
|
|
||||||
BINNAME=pki
|
|
||||||
|
|
||||||
SRCFILES=cmd/pki/*.go
|
|
||||||
|
|
||||||
all: build
|
|
||||||
|
|
||||||
build:
|
|
||||||
${GOBUILDCMD} ${GOOPTIONS} ${SRCFILES}
|
|
||||||
|
|
||||||
clean:
|
|
||||||
${RMCMD} -f ${BINNAME}
|
|
@ -10,7 +10,7 @@ PKI is a centralized Letsencrypt database server and renewer for certificate man
|
|||||||
### Build
|
### Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make
|
go build cmd/pki/pki.go
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sample config in pki.ini
|
### Sample config in pki.ini
|
||||||
@ -40,7 +40,7 @@ ovhck=
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Copyright (c) 2020, 2021 PaulBSD
|
Copyright (c) 2020, 2021, 2022 PaulBSD
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
62
ci-build.sh
62
ci-build.sh
@ -1,62 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
PROJECTNAME=pki
|
|
||||||
RELEASENAME=${PROJECTNAME}
|
|
||||||
VERSION="0"
|
|
||||||
|
|
||||||
GOOPTIONS="-mod=vendor"
|
|
||||||
SRCFILES=cmd/${PROJECTNAME}/*.go
|
|
||||||
|
|
||||||
build() {
|
|
||||||
echo "Begin of build"
|
|
||||||
if [[ ! -z $DRONE_TAG ]]
|
|
||||||
then
|
|
||||||
echo "Drone tag set, let's do a release"
|
|
||||||
VERSION=$DRONE_TAG
|
|
||||||
echo "${PROJECTNAME} ${VERSION}" > /build/VERSION
|
|
||||||
elif [[ ! -z $DRONE_TAG ]]
|
|
||||||
then
|
|
||||||
echo "Drone not set, let's only do a build"
|
|
||||||
VERSION=$DRONE_COMMIT
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -z $VERSION && ! -z $GOOS && ! -z $GOARCH ]]
|
|
||||||
then
|
|
||||||
echo "Let's set a release name"
|
|
||||||
RELEASENAME=${PROJECTNAME}-${VERSION}-${GOOS}-${GOARCH}
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building project"
|
|
||||||
go build -o ${PROJECTNAME} ${GOOPTIONS} ${SRCFILES}
|
|
||||||
|
|
||||||
if [[ ! -z $DRONE_TAG ]]
|
|
||||||
then
|
|
||||||
echo "Let's make archives"
|
|
||||||
mkdir -p /build
|
|
||||||
tar -czvf /build/${RELEASENAME}.tar.gz ${PROJECTNAME}
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Removing binary file"
|
|
||||||
rm ${PROJECTNAME}
|
|
||||||
|
|
||||||
echo "End of build"
|
|
||||||
}
|
|
||||||
|
|
||||||
clean() {
|
|
||||||
rm -rf $RELEASEDIR
|
|
||||||
}
|
|
||||||
|
|
||||||
case $1 in
|
|
||||||
"build")
|
|
||||||
build
|
|
||||||
;;
|
|
||||||
"clean")
|
|
||||||
clean
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "No options choosen"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
53
go.mod
53
go.mod
@ -1,41 +1,42 @@
|
|||||||
module git.paulbsd.com/paulbsd/pki
|
module git.paulbsd.com/paulbsd/pki
|
||||||
|
|
||||||
go 1.17
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-acme/lego/v4 v4.4.0
|
github.com/go-acme/lego/v4 v4.17.4
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.5.5 // indirect
|
github.com/labstack/echo/v4 v4.12.0
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20210406100015-1e088ea4ee04 // indirect
|
github.com/lib/pq v1.10.9
|
||||||
github.com/labstack/echo/v4 v4.5.0
|
github.com/miekg/dns v1.1.62 // indirect
|
||||||
github.com/lib/pq v1.10.3
|
|
||||||
github.com/miekg/dns v1.1.43 // indirect
|
|
||||||
github.com/onsi/ginkgo v1.16.0 // indirect
|
github.com/onsi/ginkgo v1.16.0 // indirect
|
||||||
github.com/onsi/gomega v1.11.0 // indirect
|
github.com/onsi/gomega v1.11.0 // indirect
|
||||||
github.com/smartystreets/assertions v1.2.0 // indirect
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
golang.org/x/net v0.28.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
gopkg.in/ini.v1 v1.67.0
|
||||||
gopkg.in/ini.v1 v1.62.1
|
xorm.io/builder v0.3.13 // indirect
|
||||||
xorm.io/builder v0.3.9 // indirect
|
xorm.io/xorm v1.3.9
|
||||||
xorm.io/xorm v1.2.3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/goccy/go-json v0.7.8 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/json-iterator/go v1.1.11 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/labstack/gommon v0.3.0 // indirect
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/ovh/go-ovh v1.1.0 // indirect
|
github.com/ovh/go-ovh v1.6.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
golang.org/x/mod v0.20.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.22.0 // indirect
|
||||||
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
|
golang.org/x/tools v0.24.0 // indirect
|
||||||
)
|
)
|
||||||
|
@ -2,10 +2,13 @@ package cert
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
func (e *Entry) Z() {
|
||||||
|
}
|
||||||
|
|
||||||
// Entry is the main struct for stored certificates
|
// Entry is the main struct for stored certificates
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
ID int `xorm:"pk autoincr"`
|
ID int `xorm:"pk autoincr"`
|
||||||
Domains string `xorm:"notnull"`
|
Domain string `xorm:"notnull"`
|
||||||
Certificate string `xorm:"text notnull"`
|
Certificate string `xorm:"text notnull"`
|
||||||
PrivateKey string `xorm:"text notnull"`
|
PrivateKey string `xorm:"text notnull"`
|
||||||
AuthURL string `xorm:"notnull"`
|
AuthURL string `xorm:"notnull"`
|
||||||
|
@ -60,10 +60,13 @@ func (cfg *Config) GetConfig() error {
|
|||||||
options["ovhas"] = pkisection.Key("ovhas").MustString("")
|
options["ovhas"] = pkisection.Key("ovhas").MustString("")
|
||||||
options["ovhck"] = pkisection.Key("ovhck").MustString("")
|
options["ovhck"] = pkisection.Key("ovhck").MustString("")
|
||||||
|
|
||||||
|
options["pdnsapiurl"] = pkisection.Key("pdnsapiurl").MustString("")
|
||||||
|
options["pdnsapikey"] = pkisection.Key("pdnsapikey").MustString("")
|
||||||
|
|
||||||
cfg.ACME.ProviderOptions = options
|
cfg.ACME.ProviderOptions = options
|
||||||
for k, v := range options {
|
for key, value := range options {
|
||||||
if v == "" {
|
if value == "" {
|
||||||
utils.Advice(fmt.Sprintf("OVH provider parameter %s not set", k))
|
utils.Advice(fmt.Sprintf("Provider parameter %s not set", key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +75,8 @@ func (cfg *Config) GetConfig() error {
|
|||||||
cfg.ACME.AuthURL = lego.LEDirectoryProduction
|
cfg.ACME.AuthURL = lego.LEDirectoryProduction
|
||||||
case "staging":
|
case "staging":
|
||||||
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
||||||
|
default:
|
||||||
|
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
|
"git.paulbsd.com/paulbsd/pki/src/domain"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
@ -17,7 +18,7 @@ import (
|
|||||||
func Init(cfg *config.Config) (err error) {
|
func Init(cfg *config.Config) (err error) {
|
||||||
var databaseEngine = "postgres"
|
var databaseEngine = "postgres"
|
||||||
tables := []interface{}{cert.Entry{},
|
tables := []interface{}{cert.Entry{},
|
||||||
pki.User{}}
|
pki.User{}, domain.Domain{}}
|
||||||
|
|
||||||
cfg.Db, err = xorm.NewEngine(databaseEngine,
|
cfg.Db, err = xorm.NewEngine(databaseEngine,
|
||||||
fmt.Sprintf("%s://%s:%s@%s/%s",
|
fmt.Sprintf("%s://%s:%s@%s/%s",
|
||||||
|
12
src/domain/main.go
Normal file
12
src/domain/main.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Domain describes a domain
|
||||||
|
type Domain struct {
|
||||||
|
ID int `xorm:"pk autoincr"`
|
||||||
|
Domain string `xorm:"text notnull unique(domain_provider)"`
|
||||||
|
Provider string `xorm:"text notnull unique(domain_provider)"`
|
||||||
|
Created time.Time `xorm:"created notnull"`
|
||||||
|
Updated time.Time `xorm:"updated notnull"`
|
||||||
|
}
|
@ -9,12 +9,13 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
|
"git.paulbsd.com/paulbsd/pki/src/domain"
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
)
|
)
|
||||||
@ -29,15 +30,12 @@ func (u *User) Init(cfg *config.Config) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEntry returns requested acme ressource in database relative to domain
|
// GetEntry returns requested acme ressource in database relative to domain
|
||||||
func (u *User) GetEntry(cfg *config.Config, domains []string) (Entry cert.Entry, err error) {
|
func (u *User) GetEntry(cfg *config.Config, domain *string) (Entry cert.Entry, err error) {
|
||||||
|
has, err := cfg.Db.Where("domain = ?", domain).And(
|
||||||
has, err := cfg.Db.Where("domains = ?", strings.Join(domains, ",")).And(
|
|
||||||
"auth_url = ?", cfg.ACME.AuthURL).And(
|
"auth_url = ?", cfg.ACME.AuthURL).And(
|
||||||
fmt.Sprintf("validity_end::timestamp-'%d DAY'::INTERVAL >= now()", cfg.ACME.MaxDaysBefore)).Desc(
|
fmt.Sprintf("validity_end::timestamp-'%d DAY'::INTERVAL >= now()", cfg.ACME.MaxDaysBefore)).Desc(
|
||||||
"id").Get(&Entry)
|
"id").Get(&Entry)
|
||||||
|
|
||||||
fmt.Println(has, err)
|
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
err = fmt.Errorf("entry doesn't exists")
|
err = fmt.Errorf("entry doesn't exists")
|
||||||
}
|
}
|
||||||
@ -68,12 +66,38 @@ func (u *User) HandleRegistration(cfg *config.Config, client *lego.Client) (err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RequestNewCert returns a newly requested certificate to letsencrypt
|
// RequestNewCert returns a newly requested certificate to letsencrypt
|
||||||
func (u *User) RequestNewCert(cfg *config.Config, domains []string) (certificates *certificate.Resource, err error) {
|
func (u *User) RequestNewCert(cfg *config.Config, domainnames *[]string) (certs *certificate.Resource, err error) {
|
||||||
legoconfig := lego.NewConfig(u)
|
legoconfig := lego.NewConfig(u)
|
||||||
legoconfig.CADirURL = cfg.ACME.AuthURL
|
legoconfig.CADirURL = cfg.ACME.AuthURL
|
||||||
legoconfig.Certificate.KeyType = certcrypto.RSA2048
|
legoconfig.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
|
||||||
ovhprovider, err := initProvider(cfg)
|
var dom domain.Domain
|
||||||
|
var has bool
|
||||||
|
for _, d := range *domainnames {
|
||||||
|
dom = domain.Domain{Domain: d}
|
||||||
|
if has, err = cfg.Db.Get(&dom); has {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
err = fmt.Errorf("supplied domain not in allowed domains")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var provider challenge.Provider
|
||||||
|
|
||||||
|
switch dom.Provider {
|
||||||
|
case "ovh":
|
||||||
|
provider, err = initOVHProvider(cfg)
|
||||||
|
case "pdns":
|
||||||
|
provider, err = initPowerDNSProvider(cfg)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
@ -83,7 +107,7 @@ func (u *User) RequestNewCert(cfg *config.Config, domains []string) (certificate
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.Challenge.SetDNS01Provider(ovhprovider)
|
err = client.Challenge.SetDNS01Provider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
@ -97,14 +121,15 @@ func (u *User) RequestNewCert(cfg *config.Config, domains []string) (certificate
|
|||||||
}
|
}
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
request := certificate.ObtainRequest{
|
||||||
Domains: domains,
|
Domains: *domainnames,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
certificates, err = client.Certificate.Obtain(request)
|
certs, err = client.Certificate.Obtain(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package pki
|
package pki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initProvider initialize DNS provider configuration
|
// initOVHProvider initialize DNS provider configuration
|
||||||
func initProvider(cfg *config.Config) (ovhprovider *ovh.DNSProvider, err error) {
|
func initOVHProvider(cfg *config.Config) (ovhprovider *ovh.DNSProvider, err error) {
|
||||||
ovhconfig := ovh.NewDefaultConfig()
|
ovhconfig := ovh.NewDefaultConfig()
|
||||||
|
|
||||||
ovhconfig.APIEndpoint = cfg.ACME.ProviderOptions["ovhendpoint"]
|
ovhconfig.APIEndpoint = cfg.ACME.ProviderOptions["ovhendpoint"]
|
||||||
@ -15,6 +19,24 @@ func initProvider(cfg *config.Config) (ovhprovider *ovh.DNSProvider, err error)
|
|||||||
ovhconfig.ConsumerKey = cfg.ACME.ProviderOptions["ovhck"]
|
ovhconfig.ConsumerKey = cfg.ACME.ProviderOptions["ovhck"]
|
||||||
|
|
||||||
ovhprovider, err = ovh.NewDNSProviderConfig(ovhconfig)
|
ovhprovider, err = ovh.NewDNSProviderConfig(ovhconfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// initPowerDNSProvider initialize DNS provider configuration
|
||||||
|
func initPowerDNSProvider(cfg *config.Config) (pdnsprovider *pdns.DNSProvider, err error) {
|
||||||
|
pdnsconfig := pdns.NewDefaultConfig()
|
||||||
|
|
||||||
|
pdnsconfig.Host, err = url.Parse(cfg.ACME.ProviderOptions["pdnsapiurl"])
|
||||||
|
pdnsconfig.APIKey = cfg.ACME.ProviderOptions["pdnsapikey"]
|
||||||
|
|
||||||
|
pdnsprovider, err = pdns.NewDNSProviderConfig(pdnsconfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||||
@ -30,13 +29,18 @@ func RunServer(cfg *config.Config) (err error) {
|
|||||||
e.GET("/", func(c echo.Context) error {
|
e.GET("/", func(c echo.Context) error {
|
||||||
return c.String(http.StatusOK, "Welcome to PKI software (https://git.paulbsd.com/paulbsd/pki)")
|
return c.String(http.StatusOK, "Welcome to PKI software (https://git.paulbsd.com/paulbsd/pki)")
|
||||||
})
|
})
|
||||||
e.GET("/domain/:domains", func(c echo.Context) (err error) {
|
e.POST("/cert", func(c echo.Context) (err error) {
|
||||||
var result EntryResponse
|
var request = new(EntryRequest)
|
||||||
var domains = strings.Split(c.Param("domains"), ",")
|
var result = make(map[string]EntryResponse)
|
||||||
|
err = c.Bind(&request)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return c.JSON(http.StatusInternalServerError, "error parsing request")
|
||||||
|
}
|
||||||
|
|
||||||
log.Println(fmt.Sprintf("Providing %s to user %s at %s", domains, c.Get("username"), c.RealIP()))
|
log.Printf("Providing %s to user %s at %s\n", request.Domains, c.Get("username"), c.RealIP())
|
||||||
|
|
||||||
result, err = GetCertificate(cfg, c.Get("user").(*pki.User), domains)
|
result, err = GetCertificate(cfg, c.Get("user").(*pki.User), &request.Domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.String(http.StatusInternalServerError, fmt.Sprintf("%s", err))
|
return c.String(http.StatusInternalServerError, fmt.Sprintf("%s", err))
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||||
@ -14,18 +13,24 @@ import (
|
|||||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const timeformatstring string = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
|
var domainRegex, err = regexp.Compile(`^[a-z0-9\*]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`)
|
||||||
|
|
||||||
// GetCertificate get certificate from database if exists, of request it from ACME
|
// GetCertificate get certificate from database if exists, of request it from ACME
|
||||||
func GetCertificate(cfg *config.Config, user *pki.User, domains []string) (result EntryResponse, err error) {
|
func GetCertificate(cfg *config.Config, user *pki.User, domains *[]string) (result map[string]EntryResponse, err error) {
|
||||||
err = CheckDomains(domains)
|
err = CheckDomains(domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
result = make(map[string]EntryResponse)
|
||||||
|
|
||||||
entry, err := user.GetEntry(cfg, domains)
|
firstdomain := (*domains)[0]
|
||||||
|
entry, err := user.GetEntry(cfg, &firstdomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
certs, err := user.RequestNewCert(cfg, domains)
|
certs, err := user.RequestNewCert(cfg, domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(fmt.Sprintf("Error fetching new certificate %s", err))
|
log.Printf("Error fetching new certificate %s\n", err)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
NotBefore, NotAfter, err := GetDates(certs.Certificate)
|
NotBefore, NotAfter, err := GetDates(certs.Certificate)
|
||||||
@ -33,37 +38,40 @@ func GetCertificate(cfg *config.Config, user *pki.User, domains []string) (resul
|
|||||||
log.Println("Error where parsing dates")
|
log.Println("Error where parsing dates")
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
entry := cert.Entry{Domains: strings.Join(domains, ","),
|
entry := cert.Entry{Domain: certs.Domain,
|
||||||
Certificate: string(certs.Certificate),
|
Certificate: string(certs.Certificate),
|
||||||
PrivateKey: string(certs.PrivateKey),
|
PrivateKey: string(certs.PrivateKey),
|
||||||
ValidityBegin: NotBefore,
|
ValidityBegin: NotBefore,
|
||||||
ValidityEnd: NotAfter,
|
ValidityEnd: NotAfter,
|
||||||
AuthURL: cfg.ACME.AuthURL}
|
AuthURL: cfg.ACME.AuthURL}
|
||||||
cfg.Db.Insert(&entry)
|
cfg.Db.Insert(&entry)
|
||||||
result = convertEntryToResponse(entry)
|
result[firstdomain] = convertEntryToResponse(entry)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
result = convertEntryToResponse(entry)
|
result[firstdomain] = convertEntryToResponse(entry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckDomains check if requested domains are valid
|
// CheckDomains check if requested domains are valid
|
||||||
func CheckDomains(domains []string) (err error) {
|
func CheckDomains(domains *[]string) (err error) {
|
||||||
domainRegex, err := regexp.Compile(`^[a-z0-9\*]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`)
|
for _, domain := range *domains {
|
||||||
|
err = CheckDomain(&domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range domains {
|
|
||||||
res := domainRegex.Match([]byte(d))
|
|
||||||
if !res {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("Domain %s has not a valid syntax %s, please verify", d, err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckDomain check if requested domain are valid
|
||||||
|
func CheckDomain(domain *string) (err error) {
|
||||||
|
res := domainRegex.Match([]byte(*domain))
|
||||||
|
if !res {
|
||||||
|
return fmt.Errorf("Domain %s has not a valid syntax %s, please verify", *domain, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetDates decodes NotBefore and NotAfter date of cert
|
// GetDates decodes NotBefore and NotAfter date of cert
|
||||||
func GetDates(cert []byte) (NotBefore time.Time, NotAfter time.Time, err error) {
|
func GetDates(cert []byte) (NotBefore time.Time, NotAfter time.Time, err error) {
|
||||||
block, _ := pem.Decode(cert)
|
block, _ := pem.Decode(cert)
|
||||||
@ -80,9 +88,7 @@ func GetDates(cert []byte) (NotBefore time.Time, NotAfter time.Time, err error)
|
|||||||
|
|
||||||
// convertEntryToResponse converts database ACME entry to JSON ACME entry
|
// convertEntryToResponse converts database ACME entry to JSON ACME entry
|
||||||
func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
|
func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
|
||||||
timeformatstring := "2006-01-02 15:04:05"
|
out.Domains = append(out.Domains, in.Domain)
|
||||||
|
|
||||||
out.Domains = in.Domains
|
|
||||||
out.Certificate = in.Certificate
|
out.Certificate = in.Certificate
|
||||||
out.PrivateKey = in.PrivateKey
|
out.PrivateKey = in.PrivateKey
|
||||||
out.ValidityBegin = in.ValidityBegin.Format(timeformatstring)
|
out.ValidityBegin = in.ValidityBegin.Format(timeformatstring)
|
||||||
@ -91,11 +97,16 @@ func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EntryRequest
|
||||||
|
type EntryRequest struct {
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
}
|
||||||
|
|
||||||
// EntryResponse is the struct defining JSON response from webservice
|
// EntryResponse is the struct defining JSON response from webservice
|
||||||
type EntryResponse struct {
|
type EntryResponse struct {
|
||||||
Domains string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
PrivateKey string `json:"privatekey"`
|
PrivateKey string `json:"privatekey"`
|
||||||
ValidityBegin string `json:"validitybegin"`
|
ValidityBegin string `json:"validitybegin"`
|
||||||
ValidityEnd string `json:"validityend"`
|
ValidityEnd string `json:"validityend"`
|
||||||
}
|
}
|
||||||
|
10
vendor/github.com/cenkalti/backoff/v4/.travis.yml
generated
vendored
10
vendor/github.com/cenkalti/backoff/v4/.travis.yml
generated
vendored
@ -1,10 +0,0 @@
|
|||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.13
|
|
||||||
- 1.x
|
|
||||||
- tip
|
|
||||||
before_install:
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
- go get golang.org/x/tools/cmd/cover
|
|
||||||
script:
|
|
||||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
4
vendor/github.com/cenkalti/backoff/v4/README.md
generated
vendored
4
vendor/github.com/cenkalti/backoff/v4/README.md
generated
vendored
@ -1,4 +1,4 @@
|
|||||||
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
|
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Coverage Status][coveralls image]][coveralls]
|
||||||
|
|
||||||
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
||||||
|
|
||||||
@ -21,8 +21,6 @@ Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
|
|||||||
|
|
||||||
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
|
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
|
||||||
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
||||||
[travis]: https://travis-ci.org/cenkalti/backoff
|
|
||||||
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
|
|
||||||
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
|
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
|
||||||
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
|
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
|
||||||
|
|
||||||
|
60
vendor/github.com/cenkalti/backoff/v4/exponential.go
generated
vendored
60
vendor/github.com/cenkalti/backoff/v4/exponential.go
generated
vendored
@ -71,6 +71,9 @@ type Clock interface {
|
|||||||
Now() time.Time
|
Now() time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExponentialBackOffOpts is a function type used to configure ExponentialBackOff options.
|
||||||
|
type ExponentialBackOffOpts func(*ExponentialBackOff)
|
||||||
|
|
||||||
// Default values for ExponentialBackOff.
|
// Default values for ExponentialBackOff.
|
||||||
const (
|
const (
|
||||||
DefaultInitialInterval = 500 * time.Millisecond
|
DefaultInitialInterval = 500 * time.Millisecond
|
||||||
@ -81,7 +84,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||||
func NewExponentialBackOff() *ExponentialBackOff {
|
func NewExponentialBackOff(opts ...ExponentialBackOffOpts) *ExponentialBackOff {
|
||||||
b := &ExponentialBackOff{
|
b := &ExponentialBackOff{
|
||||||
InitialInterval: DefaultInitialInterval,
|
InitialInterval: DefaultInitialInterval,
|
||||||
RandomizationFactor: DefaultRandomizationFactor,
|
RandomizationFactor: DefaultRandomizationFactor,
|
||||||
@ -91,10 +94,62 @@ func NewExponentialBackOff() *ExponentialBackOff {
|
|||||||
Stop: Stop,
|
Stop: Stop,
|
||||||
Clock: SystemClock,
|
Clock: SystemClock,
|
||||||
}
|
}
|
||||||
|
for _, fn := range opts {
|
||||||
|
fn(b)
|
||||||
|
}
|
||||||
b.Reset()
|
b.Reset()
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInitialInterval sets the initial interval between retries.
|
||||||
|
func WithInitialInterval(duration time.Duration) ExponentialBackOffOpts {
|
||||||
|
return func(ebo *ExponentialBackOff) {
|
||||||
|
ebo.InitialInterval = duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRandomizationFactor sets the randomization factor to add jitter to intervals.
|
||||||
|
func WithRandomizationFactor(randomizationFactor float64) ExponentialBackOffOpts {
|
||||||
|
return func(ebo *ExponentialBackOff) {
|
||||||
|
ebo.RandomizationFactor = randomizationFactor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMultiplier sets the multiplier for increasing the interval after each retry.
|
||||||
|
func WithMultiplier(multiplier float64) ExponentialBackOffOpts {
|
||||||
|
return func(ebo *ExponentialBackOff) {
|
||||||
|
ebo.Multiplier = multiplier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxInterval sets the maximum interval between retries.
|
||||||
|
func WithMaxInterval(duration time.Duration) ExponentialBackOffOpts {
|
||||||
|
return func(ebo *ExponentialBackOff) {
|
||||||
|
ebo.MaxInterval = duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxElapsedTime sets the maximum total time for retries.
|
||||||
|
func WithMaxElapsedTime(duration time.Duration) ExponentialBackOffOpts {
|
||||||
|
return func(ebo *ExponentialBackOff) {
|
||||||
|
ebo.MaxElapsedTime = duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRetryStopDuration sets the duration after which retries should stop.
|
||||||
|
func WithRetryStopDuration(duration time.Duration) ExponentialBackOffOpts {
|
||||||
|
return func(ebo *ExponentialBackOff) {
|
||||||
|
ebo.Stop = duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithClockProvider sets the clock used to measure time.
|
||||||
|
func WithClockProvider(clock Clock) ExponentialBackOffOpts {
|
||||||
|
return func(ebo *ExponentialBackOff) {
|
||||||
|
ebo.Clock = clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type systemClock struct{}
|
type systemClock struct{}
|
||||||
|
|
||||||
func (t systemClock) Now() time.Time {
|
func (t systemClock) Now() time.Time {
|
||||||
@ -147,6 +202,9 @@ func (b *ExponentialBackOff) incrementCurrentInterval() {
|
|||||||
// Returns a random value from the following interval:
|
// Returns a random value from the following interval:
|
||||||
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
||||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||||
|
if randomizationFactor == 0 {
|
||||||
|
return currentInterval // make sure no randomness is used when randomizationFactor is 0.
|
||||||
|
}
|
||||||
var delta = randomizationFactor * float64(currentInterval)
|
var delta = randomizationFactor * float64(currentInterval)
|
||||||
var minInterval = float64(currentInterval) - delta
|
var minInterval = float64(currentInterval) - delta
|
||||||
var maxInterval = float64(currentInterval) + delta
|
var maxInterval = float64(currentInterval) + delta
|
||||||
|
50
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
50
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
@ -5,10 +5,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
|
||||||
|
// The operation will be retried using a backoff policy if it returns an error.
|
||||||
|
type OperationWithData[T any] func() (T, error)
|
||||||
|
|
||||||
// An Operation is executing by Retry() or RetryNotify().
|
// An Operation is executing by Retry() or RetryNotify().
|
||||||
// The operation will be retried using a backoff policy if it returns an error.
|
// The operation will be retried using a backoff policy if it returns an error.
|
||||||
type Operation func() error
|
type Operation func() error
|
||||||
|
|
||||||
|
func (o Operation) withEmptyData() OperationWithData[struct{}] {
|
||||||
|
return func() (struct{}, error) {
|
||||||
|
return struct{}{}, o()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Notify is a notify-on-error function. It receives an operation error and
|
// Notify is a notify-on-error function. It receives an operation error and
|
||||||
// backoff delay if the operation failed (with an error).
|
// backoff delay if the operation failed (with an error).
|
||||||
//
|
//
|
||||||
@ -28,18 +38,41 @@ func Retry(o Operation, b BackOff) error {
|
|||||||
return RetryNotify(o, b, nil)
|
return RetryNotify(o, b, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryWithData is like Retry but returns data in the response too.
|
||||||
|
func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) {
|
||||||
|
return RetryNotifyWithData(o, b, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// RetryNotify calls notify function with the error and wait duration
|
// RetryNotify calls notify function with the error and wait duration
|
||||||
// for each failed attempt before sleep.
|
// for each failed attempt before sleep.
|
||||||
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||||
return RetryNotifyWithTimer(operation, b, notify, nil)
|
return RetryNotifyWithTimer(operation, b, notify, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryNotifyWithData is like RetryNotify but returns data in the response too.
|
||||||
|
func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) {
|
||||||
|
return doRetryNotify(operation, b, notify, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
|
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
|
||||||
// for each failed attempt before sleep.
|
// for each failed attempt before sleep.
|
||||||
// A default timer that uses system timer is used when nil is passed.
|
// A default timer that uses system timer is used when nil is passed.
|
||||||
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
|
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
|
||||||
var err error
|
_, err := doRetryNotify(operation.withEmptyData(), b, notify, t)
|
||||||
var next time.Duration
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
|
||||||
|
func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
|
||||||
|
return doRetryNotify(operation, b, notify, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
next time.Duration
|
||||||
|
res T
|
||||||
|
)
|
||||||
if t == nil {
|
if t == nil {
|
||||||
t = &defaultTimer{}
|
t = &defaultTimer{}
|
||||||
}
|
}
|
||||||
@ -52,21 +85,22 @@ func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer
|
|||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
for {
|
for {
|
||||||
if err = operation(); err == nil {
|
res, err = operation()
|
||||||
return nil
|
if err == nil {
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var permanent *PermanentError
|
var permanent *PermanentError
|
||||||
if errors.As(err, &permanent) {
|
if errors.As(err, &permanent) {
|
||||||
return permanent.Err
|
return res, permanent.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
if next = b.NextBackOff(); next == Stop {
|
if next = b.NextBackOff(); next == Stop {
|
||||||
if cerr := ctx.Err(); cerr != nil {
|
if cerr := ctx.Err(); cerr != nil {
|
||||||
return cerr
|
return res, cerr
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if notify != nil {
|
if notify != nil {
|
||||||
@ -77,7 +111,7 @@ func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return res, ctx.Err()
|
||||||
case <-t.C():
|
case <-t.C():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
vendor/github.com/go-acme/lego/v4/acme/api/account.go
generated
vendored
2
vendor/github.com/go-acme/lego/v4/acme/api/account.go
generated
vendored
@ -16,7 +16,7 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
|||||||
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
||||||
location := getLocation(resp)
|
location := getLocation(resp)
|
||||||
|
|
||||||
if len(location) > 0 {
|
if location != "" {
|
||||||
a.core.jws.SetKid(location)
|
a.core.jws.SetKid(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
vendor/github.com/go-acme/lego/v4/acme/api/api.go
generated
vendored
12
vendor/github.com/go-acme/lego/v4/acme/api/api.go
generated
vendored
@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto"
|
"crypto"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -71,7 +70,7 @@ func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// postAsGet performs an HTTP POST ("POST-as-GET") request.
|
// postAsGet performs an HTTP POST ("POST-as-GET") request.
|
||||||
// https://tools.ietf.org/html/rfc8555#section-6.3
|
// https://www.rfc-editor.org/rfc/rfc8555.html#section-6.3
|
||||||
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
|
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
|
||||||
return a.retrievablePost(uri, []byte{}, response)
|
return a.retrievablePost(uri, []byte{}, response)
|
||||||
}
|
}
|
||||||
@ -83,8 +82,6 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
|
|||||||
bo.MaxInterval = 5 * time.Second
|
bo.MaxInterval = 5 * time.Second
|
||||||
bo.MaxElapsedTime = 20 * time.Second
|
bo.MaxElapsedTime = 20 * time.Second
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
var err error
|
var err error
|
||||||
@ -96,8 +93,7 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
return backoff.Permanent(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -107,7 +103,7 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
|
|||||||
log.Infof("retry due to: %v", err)
|
log.Infof("retry due to: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := backoff.RetryNotify(operation, backoff.WithContext(bo, ctx), notify)
|
err := backoff.RetryNotify(operation, bo, notify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@ -121,7 +117,7 @@ func (a *Core) signedPost(uri string, content []byte, response interface{}) (*ht
|
|||||||
return nil, fmt.Errorf("failed to post JWS message: failed to sign content: %w", err)
|
return nil, fmt.Errorf("failed to post JWS message: failed to sign content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
signedBody := bytes.NewBufferString(signedContent.FullSerialize())
|
||||||
|
|
||||||
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
|
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
|
||||||
|
|
||||||
|
14
vendor/github.com/go-acme/lego/v4/acme/api/certificate.go
generated
vendored
14
vendor/github.com/go-acme/lego/v4/acme/api/certificate.go
generated
vendored
@ -1,10 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
@ -39,7 +40,7 @@ func (c *CertificateService) GetAll(certURL string, bundle bool) (map[string]*ac
|
|||||||
certs := map[string]*acme.RawCertificate{certURL: cert}
|
certs := map[string]*acme.RawCertificate{certURL: cert}
|
||||||
|
|
||||||
// URLs of "alternate" link relation
|
// URLs of "alternate" link relation
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.4.2
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2
|
||||||
alts := getLinks(headers, "alternate")
|
alts := getLinks(headers, "alternate")
|
||||||
|
|
||||||
for _, alt := range alts {
|
for _, alt := range alts {
|
||||||
@ -71,7 +72,7 @@ func (c *CertificateService) get(certURL string, bundle bool) (*acme.RawCertific
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
data, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp.Header, err
|
return nil, resp.Header, err
|
||||||
}
|
}
|
||||||
@ -87,12 +88,17 @@ func (c *CertificateService) getCertificateChain(cert []byte, headers http.Heade
|
|||||||
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
||||||
_, issuer := pem.Decode(cert)
|
_, issuer := pem.Decode(cert)
|
||||||
if issuer != nil {
|
if issuer != nil {
|
||||||
|
// If bundle is false, we want to return a single certificate.
|
||||||
|
// To do this, we remove the issuer cert(s) from the issued cert.
|
||||||
|
if !bundle {
|
||||||
|
cert = bytes.TrimSuffix(cert, issuer)
|
||||||
|
}
|
||||||
return &acme.RawCertificate{Cert: cert, Issuer: issuer}
|
return &acme.RawCertificate{Cert: cert, Issuer: issuer}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The issuer certificate link may be supplied via an "up" link
|
// The issuer certificate link may be supplied via an "up" link
|
||||||
// in the response headers of a new certificate.
|
// in the response headers of a new certificate.
|
||||||
// See https://tools.ietf.org/html/rfc8555#section-7.4.2
|
// See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2
|
||||||
up := getLink(headers, "up")
|
up := getLink(headers, "up")
|
||||||
|
|
||||||
issuer, err := c.getIssuerFromLink(up)
|
issuer, err := c.getIssuerFromLink(up)
|
||||||
|
2
vendor/github.com/go-acme/lego/v4/acme/api/internal/nonces/nonce_manager.go
generated
vendored
2
vendor/github.com/go-acme/lego/v4/acme/api/internal/nonces/nonce_manager.go
generated
vendored
@ -63,7 +63,7 @@ func (n *Manager) getNonce() (string, error) {
|
|||||||
return GetFromResponse(resp)
|
return GetFromResponse(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFromResponse Extracts a nonce from a HTTP response.
|
// GetFromResponse Extracts a nonce from an HTTP response.
|
||||||
func GetFromResponse(resp *http.Response) (string, error) {
|
func GetFromResponse(resp *http.Response) (string, error) {
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
return "", errors.New("nil response")
|
return "", errors.New("nil response")
|
||||||
|
2
vendor/github.com/go-acme/lego/v4/acme/api/internal/secure/jws.go
generated
vendored
2
vendor/github.com/go-acme/lego/v4/acme/api/internal/secure/jws.go
generated
vendored
@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
||||||
jose "gopkg.in/square/go-jose.v2"
|
jose "github.com/go-jose/go-jose/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWS Represents a JWS.
|
// JWS Represents a JWS.
|
||||||
|
9
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/sender.go
generated
vendored
9
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/sender.go
generated
vendored
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@ -96,7 +95,7 @@ func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if response != nil {
|
if response != nil {
|
||||||
raw, err := ioutil.ReadAll(resp.Body)
|
raw, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@ -120,7 +119,7 @@ func (d *Doer) formatUserAgent() string {
|
|||||||
|
|
||||||
func checkError(req *http.Request, resp *http.Response) error {
|
func checkError(req *http.Request, resp *http.Response) error {
|
||||||
if resp.StatusCode >= http.StatusBadRequest {
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
|
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
|
||||||
}
|
}
|
||||||
@ -134,6 +133,10 @@ func checkError(req *http.Request, resp *http.Response) error {
|
|||||||
errorDetails.Method = req.Method
|
errorDetails.Method = req.Method
|
||||||
errorDetails.URL = req.URL.String()
|
errorDetails.URL = req.URL.String()
|
||||||
|
|
||||||
|
if errorDetails.HTTPStatus == 0 {
|
||||||
|
errorDetails.HTTPStatus = resp.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
// Check for errors we handle specifically
|
// Check for errors we handle specifically
|
||||||
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
|
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
|
||||||
return &acme.NonceError{ProblemDetails: errorDetails}
|
return &acme.NonceError{ProblemDetails: errorDetails}
|
||||||
|
2
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/useragent.go
generated
vendored
2
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/useragent.go
generated
vendored
@ -5,7 +5,7 @@ package sender
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// ourUserAgent is the User-Agent of this underlying library package.
|
// ourUserAgent is the User-Agent of this underlying library package.
|
||||||
ourUserAgent = "xenolf-acme/4.4.0"
|
ourUserAgent = "xenolf-acme/4.17.4"
|
||||||
|
|
||||||
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
||||||
// values: detach|release
|
// values: detach|release
|
||||||
|
39
vendor/github.com/go-acme/lego/v4/acme/api/order.go
generated
vendored
39
vendor/github.com/go-acme/lego/v4/acme/api/order.go
generated
vendored
@ -3,21 +3,58 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OrderOptions used to create an order (optional).
|
||||||
|
type OrderOptions struct {
|
||||||
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
|
// A string uniquely identifying a previously-issued certificate which this
|
||||||
|
// order is intended to replace.
|
||||||
|
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||||
|
ReplacesCertID string
|
||||||
|
}
|
||||||
|
|
||||||
type OrderService service
|
type OrderService service
|
||||||
|
|
||||||
// New Creates a new order.
|
// New Creates a new order.
|
||||||
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
||||||
|
return o.NewWithOptions(domains, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithOptions Creates a new order.
|
||||||
|
func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acme.ExtendedOrder, error) {
|
||||||
var identifiers []acme.Identifier
|
var identifiers []acme.Identifier
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
identifiers = append(identifiers, acme.Identifier{Type: "dns", Value: domain})
|
ident := acme.Identifier{Value: domain, Type: "dns"}
|
||||||
|
|
||||||
|
if net.ParseIP(domain) != nil {
|
||||||
|
ident.Type = "ip"
|
||||||
|
}
|
||||||
|
|
||||||
|
identifiers = append(identifiers, ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
orderReq := acme.Order{Identifiers: identifiers}
|
orderReq := acme.Order{Identifiers: identifiers}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
if !opts.NotAfter.IsZero() {
|
||||||
|
orderReq.NotAfter = opts.NotAfter.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.NotBefore.IsZero() {
|
||||||
|
orderReq.NotBefore = opts.NotBefore.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.core.GetDirectory().RenewalInfo != "" {
|
||||||
|
orderReq.Replaces = opts.ReplacesCertID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var order acme.Order
|
var order acme.Order
|
||||||
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
28
vendor/github.com/go-acme/lego/v4/acme/api/renewal.go
generated
vendored
Normal file
28
vendor/github.com/go-acme/lego/v4/acme/api/renewal.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNoARI is returned when the server does not advertise a renewal info endpoint.
|
||||||
|
var ErrNoARI = errors.New("renewalInfo[get/post]: server does not advertise a renewal info endpoint")
|
||||||
|
|
||||||
|
// GetRenewalInfo GETs renewal information for a certificate from the renewalInfo endpoint.
|
||||||
|
// This is used to determine if a certificate needs to be renewed.
|
||||||
|
//
|
||||||
|
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
|
||||||
|
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
|
||||||
|
func (c *CertificateService) GetRenewalInfo(certID string) (*http.Response, error) {
|
||||||
|
if c.core.GetDirectory().RenewalInfo == "" {
|
||||||
|
return nil, ErrNoARI
|
||||||
|
}
|
||||||
|
|
||||||
|
if certID == "" {
|
||||||
|
return nil, errors.New("renewalInfo[get]: 'certID' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.core.HTTPClient.Get(c.core.GetDirectory().RenewalInfo + "/" + certID)
|
||||||
|
}
|
108
vendor/github.com/go-acme/lego/v4/acme/commons.go
generated
vendored
108
vendor/github.com/go-acme/lego/v4/acme/commons.go
generated
vendored
@ -1,5 +1,5 @@
|
|||||||
// Package acme contains all objects related the ACME endpoints.
|
// Package acme contains all objects related the ACME endpoints.
|
||||||
// https://tools.ietf.org/html/rfc8555
|
// https://www.rfc-editor.org/rfc/rfc8555.html
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -7,20 +7,38 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Challenge statuses.
|
// ACME status values of Account, Order, Authorization and Challenge objects.
|
||||||
// https://tools.ietf.org/html/rfc8555#section-7.1.6
|
// See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.6 for details.
|
||||||
const (
|
const (
|
||||||
StatusPending = "pending"
|
|
||||||
StatusInvalid = "invalid"
|
|
||||||
StatusValid = "valid"
|
|
||||||
StatusProcessing = "processing"
|
|
||||||
StatusDeactivated = "deactivated"
|
StatusDeactivated = "deactivated"
|
||||||
StatusExpired = "expired"
|
StatusExpired = "expired"
|
||||||
|
StatusInvalid = "invalid"
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusProcessing = "processing"
|
||||||
|
StatusReady = "ready"
|
||||||
StatusRevoked = "revoked"
|
StatusRevoked = "revoked"
|
||||||
|
StatusUnknown = "unknown"
|
||||||
|
StatusValid = "valid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CRL reason codes as defined in RFC 5280.
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1
|
||||||
|
const (
|
||||||
|
CRLReasonUnspecified uint = 0
|
||||||
|
CRLReasonKeyCompromise uint = 1
|
||||||
|
CRLReasonCACompromise uint = 2
|
||||||
|
CRLReasonAffiliationChanged uint = 3
|
||||||
|
CRLReasonSuperseded uint = 4
|
||||||
|
CRLReasonCessationOfOperation uint = 5
|
||||||
|
CRLReasonCertificateHold uint = 6
|
||||||
|
CRLReasonRemoveFromCRL uint = 8
|
||||||
|
CRLReasonPrivilegeWithdrawn uint = 9
|
||||||
|
CRLReasonAACompromise uint = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// Directory the ACME directory object.
|
// Directory the ACME directory object.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1
|
||||||
|
// - https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
|
||||||
type Directory struct {
|
type Directory struct {
|
||||||
NewNonceURL string `json:"newNonce"`
|
NewNonceURL string `json:"newNonce"`
|
||||||
NewAccountURL string `json:"newAccount"`
|
NewAccountURL string `json:"newAccount"`
|
||||||
@ -29,10 +47,11 @@ type Directory struct {
|
|||||||
RevokeCertURL string `json:"revokeCert"`
|
RevokeCertURL string `json:"revokeCert"`
|
||||||
KeyChangeURL string `json:"keyChange"`
|
KeyChangeURL string `json:"keyChange"`
|
||||||
Meta Meta `json:"meta"`
|
Meta Meta `json:"meta"`
|
||||||
|
RenewalInfo string `json:"renewalInfo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Meta the ACME meta object (related to Directory).
|
// Meta the ACME meta object (related to Directory).
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
// termsOfService (optional, string):
|
// termsOfService (optional, string):
|
||||||
// A URL identifying the current terms of service.
|
// A URL identifying the current terms of service.
|
||||||
@ -52,12 +71,12 @@ type Meta struct {
|
|||||||
|
|
||||||
// externalAccountRequired (optional, boolean):
|
// externalAccountRequired (optional, boolean):
|
||||||
// If this field is present and set to "true",
|
// If this field is present and set to "true",
|
||||||
// then the CA requires that all new- account requests include an "externalAccountBinding" field
|
// then the CA requires that all new-account requests include an "externalAccountBinding" field
|
||||||
// associating the new account with an external account.
|
// associating the new account with an external account.
|
||||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendedAccount a extended Account.
|
// ExtendedAccount an extended Account.
|
||||||
type ExtendedAccount struct {
|
type ExtendedAccount struct {
|
||||||
Account
|
Account
|
||||||
// Contains the value of the response header `Location`
|
// Contains the value of the response header `Location`
|
||||||
@ -65,14 +84,14 @@ type ExtendedAccount struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Account the ACME account Object.
|
// Account the ACME account Object.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.2
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.2
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.3
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3
|
||||||
type Account struct {
|
type Account struct {
|
||||||
// status (required, string):
|
// status (required, string):
|
||||||
// The status of this account.
|
// The status of this account.
|
||||||
// Possible values are: "valid", "deactivated", and "revoked".
|
// Possible values are: "valid", "deactivated", and "revoked".
|
||||||
// The value "deactivated" should be used to indicate client-initiated deactivation
|
// The value "deactivated" should be used to indicate client-initiated deactivation
|
||||||
// whereas "revoked" should be used to indicate server- initiated deactivation. (See Section 7.1.6)
|
// whereas "revoked" should be used to indicate server-initiated deactivation. (See Section 7.1.6)
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
// contact (optional, array of string):
|
// contact (optional, array of string):
|
||||||
@ -112,7 +131,7 @@ type ExtendedOrder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Order the ACME order Object.
|
// Order the ACME order Object.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.3
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.3
|
||||||
type Order struct {
|
type Order struct {
|
||||||
// status (required, string):
|
// status (required, string):
|
||||||
// The status of this order.
|
// The status of this order.
|
||||||
@ -162,10 +181,16 @@ type Order struct {
|
|||||||
// certificate (optional, string):
|
// certificate (optional, string):
|
||||||
// A URL for the certificate that has been issued in response to this order
|
// A URL for the certificate that has been issued in response to this order
|
||||||
Certificate string `json:"certificate,omitempty"`
|
Certificate string `json:"certificate,omitempty"`
|
||||||
|
|
||||||
|
// replaces (optional, string):
|
||||||
|
// replaces (string, optional): A string uniquely identifying a
|
||||||
|
// previously-issued certificate which this order is intended to replace.
|
||||||
|
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||||
|
Replaces string `json:"replaces,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorization the ACME authorization object.
|
// Authorization the ACME authorization object.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.4
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.4
|
||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
// status (required, string):
|
// status (required, string):
|
||||||
// The status of this authorization.
|
// The status of this authorization.
|
||||||
@ -207,8 +232,8 @@ type ExtendedChallenge struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Challenge the ACME challenge object.
|
// Challenge the ACME challenge object.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.1.5
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.5
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-8
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-8
|
||||||
type Challenge struct {
|
type Challenge struct {
|
||||||
// type (required, string):
|
// type (required, string):
|
||||||
// The type of challenge encoded in the object.
|
// The type of challenge encoded in the object.
|
||||||
@ -241,23 +266,23 @@ type Challenge struct {
|
|||||||
// It MUST NOT contain any characters outside the base64url alphabet,
|
// It MUST NOT contain any characters outside the base64url alphabet,
|
||||||
// and MUST NOT include base64 padding characters ("=").
|
// and MUST NOT include base64 padding characters ("=").
|
||||||
// See [RFC4086] for additional information on randomness requirements.
|
// See [RFC4086] for additional information on randomness requirements.
|
||||||
// https://tools.ietf.org/html/rfc8555#section-8.3
|
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.3
|
||||||
// https://tools.ietf.org/html/rfc8555#section-8.4
|
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.4
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc8555#section-8.1
|
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.1
|
||||||
KeyAuthorization string `json:"keyAuthorization"`
|
KeyAuthorization string `json:"keyAuthorization"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifier the ACME identifier object.
|
// Identifier the ACME identifier object.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-9.7.7
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-9.7.7
|
||||||
type Identifier struct {
|
type Identifier struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSRMessage Certificate Signing Request.
|
// CSRMessage Certificate Signing Request.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.4
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
|
||||||
type CSRMessage struct {
|
type CSRMessage struct {
|
||||||
// csr (required, string):
|
// csr (required, string):
|
||||||
// A CSR encoding the parameters for the certificate being requested [RFC2986].
|
// A CSR encoding the parameters for the certificate being requested [RFC2986].
|
||||||
@ -267,8 +292,8 @@ type CSRMessage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RevokeCertMessage a certificate revocation message.
|
// RevokeCertMessage a certificate revocation message.
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.6
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.6
|
||||||
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
|
// - https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1
|
||||||
type RevokeCertMessage struct {
|
type RevokeCertMessage struct {
|
||||||
// certificate (required, string):
|
// certificate (required, string):
|
||||||
// The certificate to be revoked, in the base64url-encoded version of the DER format.
|
// The certificate to be revoked, in the base64url-encoded version of the DER format.
|
||||||
@ -289,3 +314,36 @@ type RawCertificate struct {
|
|||||||
Cert []byte
|
Cert []byte
|
||||||
Issuer []byte
|
Issuer []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Window is a window of time.
|
||||||
|
type Window struct {
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
End time.Time `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewalInfoResponse is the response to GET requests made the renewalInfo endpoint.
|
||||||
|
// - (4.1. Getting Renewal Information) https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
|
||||||
|
type RenewalInfoResponse struct {
|
||||||
|
// SuggestedWindow contains two fields, start and end,
|
||||||
|
// whose values are timestamps which bound the window of time in which the CA recommends renewing the certificate.
|
||||||
|
SuggestedWindow Window `json:"suggestedWindow"`
|
||||||
|
// ExplanationURL is an optional URL pointing to a page which may explain why the suggested renewal window is what it is.
|
||||||
|
// For example, it may be a page explaining the CA's dynamic load-balancing strategy,
|
||||||
|
// or a page documenting which certificates are affected by a mass revocation event.
|
||||||
|
// Callers SHOULD provide this URL to their operator, if present.
|
||||||
|
ExplanationURL string `json:"explanationURL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewalInfoUpdateRequest is the JWS payload for POST requests made to the renewalInfo endpoint.
|
||||||
|
// - (4.2. RenewalInfo Objects) https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2
|
||||||
|
type RenewalInfoUpdateRequest struct {
|
||||||
|
// CertID is a composite string in the format: base64url(AKI) || '.' || base64url(Serial), where AKI is the
|
||||||
|
// certificate's authority key identifier and Serial is the certificate's serial number. For details, see:
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.1
|
||||||
|
CertID string `json:"certID"`
|
||||||
|
// Replaced is required and indicates whether or not the client considers the certificate to have been replaced.
|
||||||
|
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,
|
||||||
|
// for instance because it has been renewed and the new certificate is in use, or because it is no longer in use.
|
||||||
|
// Clients SHOULD NOT send a request where this value is false.
|
||||||
|
Replaced bool `json:"replaced"`
|
||||||
|
}
|
||||||
|
6
vendor/github.com/go-acme/lego/v4/acme/errors.go
generated
vendored
6
vendor/github.com/go-acme/lego/v4/acme/errors.go
generated
vendored
@ -11,8 +11,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ProblemDetails the problem details object.
|
// ProblemDetails the problem details object.
|
||||||
// - https://tools.ietf.org/html/rfc7807#section-3.1
|
// - https://www.rfc-editor.org/rfc/rfc7807.html#section-3.1
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-7.3.3
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3.3
|
||||||
type ProblemDetails struct {
|
type ProblemDetails struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Detail string `json:"detail,omitempty"`
|
Detail string `json:"detail,omitempty"`
|
||||||
@ -26,7 +26,7 @@ type ProblemDetails struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SubProblem a "subproblems".
|
// SubProblem a "subproblems".
|
||||||
// - https://tools.ietf.org/html/rfc8555#section-6.7.1
|
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-6.7.1
|
||||||
type SubProblem struct {
|
type SubProblem struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Detail string `json:"detail,omitempty"`
|
Detail string `json:"detail,omitempty"`
|
||||||
|
78
vendor/github.com/go-acme/lego/v4/certcrypto/crypto.go
generated
vendored
78
vendor/github.com/go-acme/lego/v4/certcrypto/crypto.go
generated
vendored
@ -14,6 +14,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ const (
|
|||||||
EC256 = KeyType("P256")
|
EC256 = KeyType("P256")
|
||||||
EC384 = KeyType("P384")
|
EC384 = KeyType("P384")
|
||||||
RSA2048 = KeyType("2048")
|
RSA2048 = KeyType("2048")
|
||||||
|
RSA3072 = KeyType("3072")
|
||||||
RSA4096 = KeyType("4096")
|
RSA4096 = KeyType("4096")
|
||||||
RSA8192 = KeyType("8192")
|
RSA8192 = KeyType("8192")
|
||||||
)
|
)
|
||||||
@ -82,9 +85,12 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
|||||||
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
|
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
|
||||||
// Borrowed from Go standard library, to handle various private key and PEM block types.
|
// Borrowed from Go standard library, to handle various private key and PEM block types.
|
||||||
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
|
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
|
||||||
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
|
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238
|
||||||
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||||
keyBlockDER, _ := pem.Decode(key)
|
keyBlockDER, _ := pem.Decode(key)
|
||||||
|
if keyBlockDER == nil {
|
||||||
|
return nil, errors.New("invalid PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
|
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
|
||||||
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
|
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
|
||||||
@ -118,6 +124,8 @@ func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
|||||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
case RSA2048:
|
case RSA2048:
|
||||||
return rsa.GenerateKey(rand.Reader, 2048)
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
case RSA3072:
|
||||||
|
return rsa.GenerateKey(rand.Reader, 3072)
|
||||||
case RSA4096:
|
case RSA4096:
|
||||||
return rsa.GenerateKey(rand.Reader, 4096)
|
return rsa.GenerateKey(rand.Reader, 4096)
|
||||||
case RSA8192:
|
case RSA8192:
|
||||||
@ -128,9 +136,20 @@ func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
||||||
|
var dnsNames []string
|
||||||
|
var ipAddresses []net.IP
|
||||||
|
for _, altname := range san {
|
||||||
|
if ip := net.ParseIP(altname); ip != nil {
|
||||||
|
ipAddresses = append(ipAddresses, ip)
|
||||||
|
} else {
|
||||||
|
dnsNames = append(dnsNames, altname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template := x509.CertificateRequest{
|
template := x509.CertificateRequest{
|
||||||
Subject: pkix.Name{CommonName: domain},
|
Subject: pkix.Name{CommonName: domain},
|
||||||
DNSNames: san,
|
DNSNames: dnsNames,
|
||||||
|
IPAddresses: ipAddresses,
|
||||||
}
|
}
|
||||||
|
|
||||||
if mustStaple {
|
if mustStaple {
|
||||||
@ -198,6 +217,26 @@ func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
|
|||||||
return x509.ParseCertificate(pemBlock.Bytes)
|
return x509.ParseCertificate(pemBlock.Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCertificateMainDomain(cert *x509.Certificate) (string, error) {
|
||||||
|
return getMainDomain(cert.Subject, cert.DNSNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCSRMainDomain(cert *x509.CertificateRequest) (string, error) {
|
||||||
|
return getMainDomain(cert.Subject, cert.DNSNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMainDomain(subject pkix.Name, dnsNames []string) (string, error) {
|
||||||
|
if subject.CommonName == "" && len(dnsNames) == 0 {
|
||||||
|
return "", errors.New("missing domain")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subject.CommonName != "" {
|
||||||
|
return subject.CommonName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnsNames[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
func ExtractDomains(cert *x509.Certificate) []string {
|
func ExtractDomains(cert *x509.Certificate) []string {
|
||||||
var domains []string
|
var domains []string
|
||||||
if cert.Subject.CommonName != "" {
|
if cert.Subject.CommonName != "" {
|
||||||
@ -212,6 +251,13 @@ func ExtractDomains(cert *x509.Certificate) []string {
|
|||||||
domains = append(domains, sanDomain)
|
domains = append(domains, sanDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commonNameIP := net.ParseIP(cert.Subject.CommonName)
|
||||||
|
for _, sanIP := range cert.IPAddresses {
|
||||||
|
if !commonNameIP.Equal(sanIP) {
|
||||||
|
domains = append(domains, sanIP.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return domains
|
return domains
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +269,7 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
|||||||
|
|
||||||
// loop over the SubjectAltName DNS names
|
// loop over the SubjectAltName DNS names
|
||||||
for _, sanName := range csr.DNSNames {
|
for _, sanName := range csr.DNSNames {
|
||||||
if containsSAN(domains, sanName) {
|
if slices.Contains(domains, sanName) {
|
||||||
// Duplicate; skip this name
|
// Duplicate; skip this name
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -232,16 +278,14 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
|||||||
domains = append(domains, sanName)
|
domains = append(domains, sanName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return domains
|
cnip := net.ParseIP(csr.Subject.CommonName)
|
||||||
}
|
for _, sanIP := range csr.IPAddresses {
|
||||||
|
if !cnip.Equal(sanIP) {
|
||||||
func containsSAN(domains []string, sanName string) bool {
|
domains = append(domains, sanIP.String())
|
||||||
for _, existingName := range domains {
|
|
||||||
if existingName == sanName {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
return domains
|
||||||
}
|
}
|
||||||
|
|
||||||
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||||
@ -261,7 +305,7 @@ func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if expiration.IsZero() {
|
if expiration.IsZero() {
|
||||||
expiration = time.Now().Add(365)
|
expiration = time.Now().AddDate(1, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
@ -274,9 +318,15 @@ func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain st
|
|||||||
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
DNSNames: []string{domain},
|
|
||||||
ExtraExtensions: extensions,
|
ExtraExtensions: extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handling SAN filling as type suspected
|
||||||
|
if ip := net.ParseIP(domain); ip != nil {
|
||||||
|
template.IPAddresses = []net.IP{ip}
|
||||||
|
} else {
|
||||||
|
template.DNSNames = []string{domain}
|
||||||
|
}
|
||||||
|
|
||||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||||
}
|
}
|
||||||
|
19
vendor/github.com/go-acme/lego/v4/certificate/authorization.go
generated
vendored
19
vendor/github.com/go-acme/lego/v4/certificate/authorization.go
generated
vendored
@ -12,6 +12,7 @@ const (
|
|||||||
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
|
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
|
||||||
// From the documentation the limitation is 20 requests per second,
|
// From the documentation the limitation is 20 requests per second,
|
||||||
// but using 20 as value doesn't work but 18 do.
|
// but using 20 as value doesn't work but 18 do.
|
||||||
|
// https://letsencrypt.org/docs/rate-limits/
|
||||||
overallRequestLimit = 18
|
overallRequestLimit = 18
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,13 +36,14 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
|||||||
}
|
}
|
||||||
|
|
||||||
var responses []acme.Authorization
|
var responses []acme.Authorization
|
||||||
failures := make(obtainError)
|
|
||||||
for i := 0; i < len(order.Authorizations); i++ {
|
failures := newObtainError()
|
||||||
|
for range len(order.Authorizations) {
|
||||||
select {
|
select {
|
||||||
case res := <-resc:
|
case res := <-resc:
|
||||||
responses = append(responses, res)
|
responses = append(responses, res)
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
failures[err.Domain] = err.Error
|
failures.Add(err.Domain, err.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,15 +54,10 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
|||||||
close(resc)
|
close(resc)
|
||||||
close(errc)
|
close(errc)
|
||||||
|
|
||||||
// be careful to not return an empty failures map;
|
return responses, failures.Join()
|
||||||
// even if empty, they become non-nil error values
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return responses, failures
|
|
||||||
}
|
|
||||||
return responses, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
|
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) {
|
||||||
for _, authzURL := range order.Authorizations {
|
for _, authzURL := range order.Authorizations {
|
||||||
auth, err := c.core.Authorizations.Get(authzURL)
|
auth, err := c.core.Authorizations.Get(authzURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,7 +65,7 @@ func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.Status == acme.StatusValid {
|
if auth.Status == acme.StatusValid && !force {
|
||||||
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
|
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
200
vendor/github.com/go-acme/lego/v4/certificate/certificates.go
generated
vendored
200
vendor/github.com/go-acme/lego/v4/certificate/certificates.go
generated
vendored
@ -7,7 +7,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -49,22 +49,44 @@ type Resource struct {
|
|||||||
// If you do not want that you can supply your own private key in the privateKey parameter.
|
// If you do not want that you can supply your own private key in the privateKey parameter.
|
||||||
// If this parameter is non-nil it will be used instead of generating a new one.
|
// If this parameter is non-nil it will be used instead of generating a new one.
|
||||||
//
|
//
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
//
|
||||||
|
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
||||||
type ObtainRequest struct {
|
type ObtainRequest struct {
|
||||||
Domains []string
|
Domains []string
|
||||||
Bundle bool
|
PrivateKey crypto.PrivateKey
|
||||||
PrivateKey crypto.PrivateKey
|
MustStaple bool
|
||||||
MustStaple bool
|
|
||||||
PreferredChain string
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
|
Bundle bool
|
||||||
|
PreferredChain string
|
||||||
|
AlwaysDeactivateAuthorizations bool
|
||||||
|
// A string uniquely identifying a previously-issued certificate which this
|
||||||
|
// order is intended to replace.
|
||||||
|
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||||
|
ReplacesCertID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
|
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
|
||||||
//
|
//
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
//
|
||||||
|
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
||||||
type ObtainForCSRRequest struct {
|
type ObtainForCSRRequest struct {
|
||||||
CSR *x509.CertificateRequest
|
CSR *x509.CertificateRequest
|
||||||
Bundle bool
|
|
||||||
PreferredChain string
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
|
Bundle bool
|
||||||
|
PreferredChain string
|
||||||
|
AlwaysDeactivateAuthorizations bool
|
||||||
|
// A string uniquely identifying a previously-issued certificate which this
|
||||||
|
// order is intended to replace.
|
||||||
|
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||||
|
ReplacesCertID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolver interface {
|
type resolver interface {
|
||||||
@ -109,7 +131,13 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
|||||||
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
order, err := c.core.Orders.New(domains)
|
orderOpts := &api.OrderOptions{
|
||||||
|
NotBefore: request.NotBefore,
|
||||||
|
NotAfter: request.NotAfter,
|
||||||
|
ReplacesCertID: request.ReplacesCertID,
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -117,33 +145,32 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
|||||||
authz, err := c.getAuthorizations(order)
|
authz, err := c.getAuthorizations(order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order)
|
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.resolver.Solve(authz)
|
err = c.resolver.Solve(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order)
|
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
failures := make(obtainError)
|
failures := newObtainError()
|
||||||
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain)
|
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, auth := range authz {
|
for _, auth := range authz {
|
||||||
failures[challenge.GetTargetedDomain(auth)] = err
|
failures.Add(challenge.GetTargetedDomain(auth), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not return an empty failures map, because
|
if request.AlwaysDeactivateAuthorizations {
|
||||||
// it would still be a non-nil error value
|
c.deactivateAuthorizations(order, true)
|
||||||
if len(failures) > 0 {
|
|
||||||
return cert, failures
|
|
||||||
}
|
}
|
||||||
return cert, nil
|
|
||||||
|
return cert, failures.Join()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
||||||
@ -170,7 +197,13 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
|||||||
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
order, err := c.core.Orders.New(domains)
|
orderOpts := &api.OrderOptions{
|
||||||
|
NotBefore: request.NotBefore,
|
||||||
|
NotAfter: request.NotAfter,
|
||||||
|
ReplacesCertID: request.ReplacesCertID,
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -178,38 +211,37 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
|||||||
authz, err := c.getAuthorizations(order)
|
authz, err := c.getAuthorizations(order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order)
|
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.resolver.Solve(authz)
|
err = c.resolver.Solve(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order)
|
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
failures := make(obtainError)
|
failures := newObtainError()
|
||||||
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain)
|
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, auth := range authz {
|
for _, auth := range authz {
|
||||||
failures[challenge.GetTargetedDomain(auth)] = err
|
failures.Add(challenge.GetTargetedDomain(auth), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if request.AlwaysDeactivateAuthorizations {
|
||||||
|
c.deactivateAuthorizations(order, true)
|
||||||
|
}
|
||||||
|
|
||||||
if cert != nil {
|
if cert != nil {
|
||||||
// Add the CSR to the certificate so that it can be used for renewals.
|
// Add the CSR to the certificate so that it can be used for renewals.
|
||||||
cert.CSR = certcrypto.PEMEncode(request.CSR)
|
cert.CSR = certcrypto.PEMEncode(request.CSR)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not return an empty failures map,
|
return cert, failures.Join()
|
||||||
// because it would still be a non-nil error value
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return cert, failures
|
|
||||||
}
|
|
||||||
return cert, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) {
|
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) {
|
||||||
@ -221,16 +253,23 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine certificate name(s) based on the authorization resources
|
commonName := ""
|
||||||
commonName := domains[0]
|
if len(domains[0]) <= 64 {
|
||||||
|
commonName = domains[0]
|
||||||
|
}
|
||||||
|
|
||||||
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
||||||
// https://tools.ietf.org/html/rfc8555#section-7.4
|
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
|
||||||
// says:
|
// says:
|
||||||
// Clients SHOULD NOT make any assumptions about the sort order of
|
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||||
// "identifiers" or "authorizations" elements in the returned order
|
// "identifiers" or "authorizations" elements in the returned order
|
||||||
// object.
|
// object.
|
||||||
san := []string{commonName}
|
|
||||||
|
var san []string
|
||||||
|
if commonName != "" {
|
||||||
|
san = append(san, commonName)
|
||||||
|
}
|
||||||
|
|
||||||
for _, auth := range order.Identifiers {
|
for _, auth := range order.Identifiers {
|
||||||
if auth.Value != commonName {
|
if auth.Value != commonName {
|
||||||
san = append(san, auth.Value)
|
san = append(san, auth.Value)
|
||||||
@ -252,15 +291,14 @@ func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
commonName := domains[0]
|
|
||||||
certRes := &Resource{
|
certRes := &Resource{
|
||||||
Domain: commonName,
|
Domain: domains[0],
|
||||||
CertURL: respOrder.Certificate,
|
CertURL: respOrder.Certificate,
|
||||||
PrivateKey: privateKeyPem,
|
PrivateKey: privateKeyPem,
|
||||||
}
|
}
|
||||||
|
|
||||||
if respOrder.Status == acme.StatusValid {
|
if respOrder.Status == acme.StatusValid {
|
||||||
// if the certificate is available right away, short cut!
|
// if the certificate is available right away, shortcut!
|
||||||
ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain)
|
ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain)
|
||||||
if errR != nil {
|
if errR != nil {
|
||||||
return nil, errR
|
return nil, errR
|
||||||
@ -349,6 +387,11 @@ func (c *Certifier) checkResponse(order acme.ExtendedOrder, certRes *Resource, b
|
|||||||
|
|
||||||
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||||
func (c *Certifier) Revoke(cert []byte) error {
|
func (c *Certifier) Revoke(cert []byte) error {
|
||||||
|
return c.RevokeWithReason(cert, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeWithReason takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||||
|
func (c *Certifier) RevokeWithReason(cert []byte, reason *uint) error {
|
||||||
certificates, err := certcrypto.ParsePEMBundle(cert)
|
certificates, err := certcrypto.ParsePEMBundle(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -361,11 +404,24 @@ func (c *Certifier) Revoke(cert []byte) error {
|
|||||||
|
|
||||||
revokeMsg := acme.RevokeCertMessage{
|
revokeMsg := acme.RevokeCertMessage{
|
||||||
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
|
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
|
||||||
|
Reason: reason,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.core.Certificates.Revoke(revokeMsg)
|
return c.core.Certificates.Revoke(revokeMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenewOptions options used by Certifier.RenewWithOptions.
|
||||||
|
type RenewOptions struct {
|
||||||
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
|
// If true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
Bundle bool
|
||||||
|
PreferredChain string
|
||||||
|
AlwaysDeactivateAuthorizations bool
|
||||||
|
// Not supported for CSR request.
|
||||||
|
MustStaple bool
|
||||||
|
}
|
||||||
|
|
||||||
// Renew takes a Resource and tries to renew the certificate.
|
// Renew takes a Resource and tries to renew the certificate.
|
||||||
//
|
//
|
||||||
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
||||||
@ -376,7 +432,26 @@ func (c *Certifier) Revoke(cert []byte) error {
|
|||||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
//
|
//
|
||||||
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||||
|
// Deprecated: use RenewWithOptions instead.
|
||||||
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) {
|
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) {
|
||||||
|
return c.RenewWithOptions(certRes, &RenewOptions{
|
||||||
|
Bundle: bundle,
|
||||||
|
PreferredChain: preferredChain,
|
||||||
|
MustStaple: mustStaple,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewWithOptions takes a Resource and tries to renew the certificate.
|
||||||
|
//
|
||||||
|
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
||||||
|
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
||||||
|
// If the server does not provide us with a new cert on a GET request to the CertURL
|
||||||
|
// this function will start a new-cert flow where a new certificate gets generated.
|
||||||
|
//
|
||||||
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
//
|
||||||
|
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||||
|
func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (*Resource, error) {
|
||||||
// Input certificate is PEM encoded.
|
// Input certificate is PEM encoded.
|
||||||
// Decode it here as we may need the decoded cert later on in the renewal process.
|
// Decode it here as we may need the decoded cert later on in the renewal process.
|
||||||
// The input may be a bundle or a single certificate.
|
// The input may be a bundle or a single certificate.
|
||||||
@ -403,11 +478,17 @@ func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredCh
|
|||||||
return nil, errP
|
return nil, errP
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.ObtainForCSR(ObtainForCSRRequest{
|
request := ObtainForCSRRequest{CSR: csr}
|
||||||
CSR: csr,
|
|
||||||
Bundle: bundle,
|
if options != nil {
|
||||||
PreferredChain: preferredChain,
|
request.NotBefore = options.NotBefore
|
||||||
})
|
request.NotAfter = options.NotAfter
|
||||||
|
request.Bundle = options.Bundle
|
||||||
|
request.PreferredChain = options.PreferredChain
|
||||||
|
request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ObtainForCSR(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
var privateKey crypto.PrivateKey
|
var privateKey crypto.PrivateKey
|
||||||
@ -418,13 +499,21 @@ func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query := ObtainRequest{
|
request := ObtainRequest{
|
||||||
Domains: certcrypto.ExtractDomains(x509Cert),
|
Domains: certcrypto.ExtractDomains(x509Cert),
|
||||||
Bundle: bundle,
|
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
MustStaple: mustStaple,
|
|
||||||
}
|
}
|
||||||
return c.Obtain(query)
|
|
||||||
|
if options != nil {
|
||||||
|
request.MustStaple = options.MustStaple
|
||||||
|
request.NotBefore = options.NotBefore
|
||||||
|
request.NotAfter = options.NotAfter
|
||||||
|
request.Bundle = options.Bundle
|
||||||
|
request.PreferredChain = options.PreferredChain
|
||||||
|
request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Obtain(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||||
@ -465,7 +554,7 @@ func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
issuerBytes, errC := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
if errC != nil {
|
if errC != nil {
|
||||||
return nil, nil, errC
|
return nil, nil, errC
|
||||||
}
|
}
|
||||||
@ -494,7 +583,7 @@ func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
ocspResBytes, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -525,8 +614,13 @@ func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
domain, err := certcrypto.GetCertificateMainDomain(x509Certs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Resource{
|
return &Resource{
|
||||||
Domain: x509Certs[0].Subject.CommonName,
|
Domain: domain,
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
IssuerCertificate: issuer,
|
IssuerCertificate: issuer,
|
||||||
CertURL: url,
|
CertURL: url,
|
||||||
@ -560,11 +654,11 @@ func checkOrderStatus(order acme.ExtendedOrder) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc8555#section-7.1.4
|
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.4
|
||||||
// The domain name MUST be encoded in the form in which it would appear in a certificate.
|
// The domain name MUST be encoded in the form in which it would appear in a certificate.
|
||||||
// That is, it MUST be encoded according to the rules in Section 7 of [RFC5280].
|
// That is, it MUST be encoded according to the rules in Section 7 of [RFC5280].
|
||||||
//
|
//
|
||||||
// https://tools.ietf.org/html/rfc5280#section-7
|
// https://www.rfc-editor.org/rfc/rfc5280.html#section-7
|
||||||
func sanitizeDomain(domains []string) []string {
|
func sanitizeDomain(domains []string) []string {
|
||||||
var sanitizedDomains []string
|
var sanitizedDomains []string
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
|
36
vendor/github.com/go-acme/lego/v4/certificate/errors.go
generated
vendored
36
vendor/github.com/go-acme/lego/v4/certificate/errors.go
generated
vendored
@ -1,27 +1,37 @@
|
|||||||
package certificate
|
package certificate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// obtainError is returned when there are specific errors available per domain.
|
type obtainError struct {
|
||||||
type obtainError map[string]error
|
data map[string]error
|
||||||
|
}
|
||||||
|
|
||||||
func (e obtainError) Error() string {
|
func newObtainError() *obtainError {
|
||||||
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
return &obtainError{data: make(map[string]error)}
|
||||||
|
}
|
||||||
|
|
||||||
var domains []string
|
func (e *obtainError) Add(domain string, err error) {
|
||||||
for domain := range e {
|
e.data[domain] = err
|
||||||
domains = append(domains, domain)
|
}
|
||||||
|
|
||||||
|
func (e *obtainError) Join() error {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
sort.Strings(domains)
|
|
||||||
|
|
||||||
for _, domain := range domains {
|
if len(e.data) == 0 {
|
||||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
return nil
|
||||||
}
|
}
|
||||||
return buffer.String()
|
|
||||||
|
var err error
|
||||||
|
for d, e := range e.data {
|
||||||
|
err = errors.Join(err, fmt.Errorf("%s: %w", d, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error: one or more domains had a problem:\n%w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type domainError struct {
|
type domainError struct {
|
||||||
|
129
vendor/github.com/go-acme/lego/v4/certificate/renewal.go
generated
vendored
Normal file
129
vendor/github.com/go-acme/lego/v4/certificate/renewal.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package certificate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RenewalInfoRequest contains the necessary renewal information.
|
||||||
|
type RenewalInfoRequest struct {
|
||||||
|
Cert *x509.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewalInfoResponse is a wrapper around acme.RenewalInfoResponse that provides a method for determining when to renew a certificate.
|
||||||
|
type RenewalInfoResponse struct {
|
||||||
|
acme.RenewalInfoResponse
|
||||||
|
|
||||||
|
// RetryAfter header indicating the polling interval that the ACME server recommends.
|
||||||
|
// Conforming clients SHOULD query the renewalInfo URL again after the RetryAfter period has passed,
|
||||||
|
// as the server may provide a different suggestedWindow.
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2
|
||||||
|
RetryAfter time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldRenewAt determines the optimal renewal time based on the current time (UTC),renewal window suggest by ARI, and the client's willingness to sleep.
|
||||||
|
// It returns a pointer to a time.Time value indicating when the renewal should be attempted or nil if deferred until the next normal wake time.
|
||||||
|
// This method implements the RECOMMENDED algorithm described in draft-ietf-acme-ari.
|
||||||
|
//
|
||||||
|
// - (4.1-11. Getting Renewal Information) https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
|
||||||
|
func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.Duration) *time.Time {
|
||||||
|
// Explicitly convert all times to UTC.
|
||||||
|
now = now.UTC()
|
||||||
|
start := r.SuggestedWindow.Start.UTC()
|
||||||
|
end := r.SuggestedWindow.End.UTC()
|
||||||
|
|
||||||
|
// Select a uniform random time within the suggested window.
|
||||||
|
window := end.Sub(start)
|
||||||
|
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
||||||
|
rt := start.Add(randomDuration)
|
||||||
|
|
||||||
|
// If the selected time is in the past, attempt renewal immediately.
|
||||||
|
if rt.Before(now) {
|
||||||
|
return &now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if the client can schedule itself to attempt renewal at exactly the selected time, do so.
|
||||||
|
willingToSleepUntil := now.Add(willingToSleep)
|
||||||
|
if willingToSleepUntil.After(rt) || willingToSleepUntil.Equal(rt) {
|
||||||
|
return &rt
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Otherwise, if the selected time is before the next time that the client would wake up normally, attempt renewal immediately.
|
||||||
|
|
||||||
|
// Otherwise, sleep until the next normal wake time, re-check ARI, and return to Step 1.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRenewalInfo sends a request to the ACME server's renewalInfo endpoint to obtain a suggested renewal window.
|
||||||
|
// The caller MUST provide the certificate and issuer certificate for the certificate they wish to renew.
|
||||||
|
// The caller should attempt to renew the certificate at the time indicated by the ShouldRenewAt method of the returned RenewalInfoResponse object.
|
||||||
|
//
|
||||||
|
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
|
||||||
|
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
|
||||||
|
func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse, error) {
|
||||||
|
certID, err := MakeARICertID(req.Cert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error making certID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.core.Certificates.GetRenewalInfo(certID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var info RenewalInfoResponse
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if retry := resp.Header.Get("Retry-After"); retry != "" {
|
||||||
|
info.RetryAfter, err = time.ParseDuration(retry + "s")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeARICertID constructs a certificate identifier as described in draft-ietf-acme-ari-03, section 4.1.
|
||||||
|
func MakeARICertID(leaf *x509.Certificate) (string, error) {
|
||||||
|
if leaf == nil {
|
||||||
|
return "", errors.New("leaf certificate is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the Serial Number into DER.
|
||||||
|
der, err := asn1.Marshal(leaf.SerialNumber)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the DER encoded bytes are sufficient (at least 3 bytes: tag,
|
||||||
|
// length, and value).
|
||||||
|
if len(der) < 3 {
|
||||||
|
return "", errors.New("invalid DER encoding of serial number")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract only the integer bytes from the DER encoded Serial Number
|
||||||
|
// Skipping the first 2 bytes (tag and length).
|
||||||
|
serial := base64.RawURLEncoding.EncodeToString(der[2:])
|
||||||
|
|
||||||
|
// Convert the Authority Key Identifier to base64url encoding without
|
||||||
|
// padding.
|
||||||
|
aki := base64.RawURLEncoding.EncodeToString(leaf.AuthorityKeyId)
|
||||||
|
|
||||||
|
// Construct the final identifier by concatenating AKI and Serial Number.
|
||||||
|
return fmt.Sprintf("%s.%s", aki, serial), nil
|
||||||
|
}
|
6
vendor/github.com/go-acme/lego/v4/challenge/challenges.go
generated
vendored
6
vendor/github.com/go-acme/lego/v4/challenge/challenges.go
generated
vendored
@ -10,15 +10,15 @@ import (
|
|||||||
type Type string
|
type Type string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.3
|
// HTTP01 is the "http-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8555.html#section-8.3
|
||||||
// Note: ChallengePath returns the URL path to fulfill this challenge.
|
// Note: ChallengePath returns the URL path to fulfill this challenge.
|
||||||
HTTP01 = Type("http-01")
|
HTTP01 = Type("http-01")
|
||||||
|
|
||||||
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.4
|
// DNS01 is the "dns-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8555.html#section-8.4
|
||||||
// Note: GetRecord returns a DNS record which will fulfill this challenge.
|
// Note: GetRecord returns a DNS record which will fulfill this challenge.
|
||||||
DNS01 = Type("dns-01")
|
DNS01 = Type("dns-01")
|
||||||
|
|
||||||
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07
|
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8737.html
|
||||||
TLSALPN01 = Type("tls-alpn-01")
|
TLSALPN01 = Type("tls-alpn-01")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/cname.go
generated
vendored
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/cname.go
generated
vendored
@ -1,12 +1,16 @@
|
|||||||
package dns01
|
package dns01
|
||||||
|
|
||||||
import "github.com/miekg/dns"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
// Update FQDN with CNAME if any.
|
// Update FQDN with CNAME if any.
|
||||||
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
|
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
|
||||||
for _, rr := range r.Answer {
|
for _, rr := range r.Answer {
|
||||||
if cn, ok := rr.(*dns.CNAME); ok {
|
if cn, ok := rr.(*dns.CNAME); ok {
|
||||||
if cn.Hdr.Name == fqdn {
|
if strings.EqualFold(cn.Hdr.Name, fqdn) {
|
||||||
return cn.Target
|
return cn.Target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
73
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge.go
generated
vendored
73
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge.go
generated
vendored
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
@ -114,7 +115,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fqdn, value := GetRecord(authz.Identifier.Value, keyAuth)
|
info := GetChallengeInfo(authz.Identifier.Value, keyAuth)
|
||||||
|
|
||||||
var timeout, interval time.Duration
|
var timeout, interval time.Duration
|
||||||
switch provider := c.provider.(type) {
|
switch provider := c.provider.(type) {
|
||||||
@ -124,12 +125,12 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
|||||||
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
|
log.Infof("[%s] acme: Checking DNS record propagation. [nameservers=%s]", domain, strings.Join(recursiveNameservers, ","))
|
||||||
|
|
||||||
time.Sleep(interval)
|
time.Sleep(interval)
|
||||||
|
|
||||||
err = wait.For("propagation", timeout, interval, func() (bool, error) {
|
err = wait.For("propagation", timeout, interval, func() (bool, error) {
|
||||||
stop, errP := c.preCheck.call(domain, fqdn, value)
|
stop, errP := c.preCheck.call(domain, info.EffectiveFQDN, info.Value)
|
||||||
if !stop || errP != nil {
|
if !stop || errP != nil {
|
||||||
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
|
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
|
||||||
}
|
}
|
||||||
@ -172,19 +173,67 @@ type sequential interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge.
|
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge.
|
||||||
|
// Deprecated: use GetChallengeInfo instead.
|
||||||
func GetRecord(domain, keyAuth string) (fqdn, value string) {
|
func GetRecord(domain, keyAuth string) (fqdn, value string) {
|
||||||
|
info := GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
|
return info.EffectiveFQDN, info.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChallengeInfo contains the information use to create the TXT record.
|
||||||
|
type ChallengeInfo struct {
|
||||||
|
// FQDN is the full-qualified challenge domain (i.e. `_acme-challenge.[domain].`)
|
||||||
|
FQDN string
|
||||||
|
|
||||||
|
// EffectiveFQDN contains the resulting FQDN after the CNAMEs resolutions.
|
||||||
|
EffectiveFQDN string
|
||||||
|
|
||||||
|
// Value contains the value for the TXT record.
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChallengeInfo returns information used to create a DNS record which will fulfill the `dns-01` challenge.
|
||||||
|
func GetChallengeInfo(domain, keyAuth string) ChallengeInfo {
|
||||||
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
||||||
// base64URL encoding without padding
|
// base64URL encoding without padding
|
||||||
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
value := base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
||||||
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
|
||||||
|
|
||||||
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_CNAME_SUPPORT")); ok {
|
ok, _ := strconv.ParseBool(os.Getenv("LEGO_DISABLE_CNAME_SUPPORT"))
|
||||||
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
|
|
||||||
// Check if the domain has CNAME then return that
|
return ChallengeInfo{
|
||||||
if err == nil && r.Rcode == dns.RcodeSuccess {
|
Value: value,
|
||||||
fqdn = updateDomainWithCName(r, fqdn)
|
FQDN: getChallengeFQDN(domain, false),
|
||||||
}
|
EffectiveFQDN: getChallengeFQDN(domain, !ok),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChallengeFQDN(domain string, followCNAME bool) string {
|
||||||
|
fqdn := fmt.Sprintf("_acme-challenge.%s.", domain)
|
||||||
|
|
||||||
|
if !followCNAME {
|
||||||
|
return fqdn
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
// recursion counter so it doesn't spin out of control
|
||||||
|
for range 50 {
|
||||||
|
// Keep following CNAMEs
|
||||||
|
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
|
||||||
|
|
||||||
|
if err != nil || r.Rcode != dns.RcodeSuccess {
|
||||||
|
// No more CNAME records to follow, exit
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the domain has CNAME then use that
|
||||||
|
cname := updateDomainWithCName(r, fqdn)
|
||||||
|
if cname == fqdn {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Found CNAME entry for %q: %q", fqdn, cname)
|
||||||
|
|
||||||
|
fqdn = cname
|
||||||
|
}
|
||||||
|
|
||||||
|
return fqdn
|
||||||
}
|
}
|
||||||
|
23
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge_manual.go
generated
vendored
23
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge_manual.go
generated
vendored
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dnsTemplate = `%s %d IN TXT "%s"`
|
dnsTemplate = `%s %d IN TXT %q`
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSProviderManual is an implementation of the ChallengeProvider interface.
|
// DNSProviderManual is an implementation of the ChallengeProvider interface.
|
||||||
@ -21,33 +21,36 @@ func NewDNSProviderManual() (*DNSProviderManual, error) {
|
|||||||
|
|
||||||
// Present prints instructions for manually creating the TXT record.
|
// Present prints instructions for manually creating the TXT record.
|
||||||
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
||||||
fqdn, value := GetRecord(domain, keyAuth)
|
info := GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
authZone, err := FindZoneByFqdn(fqdn)
|
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("manual: could not find zone: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
|
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
|
||||||
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, value)
|
fmt.Printf(dnsTemplate+"\n", info.EffectiveFQDN, DefaultTTL, info.Value)
|
||||||
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
||||||
|
|
||||||
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
|
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("manual: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp prints instructions for manually removing the TXT record.
|
// CleanUp prints instructions for manually removing the TXT record.
|
||||||
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
||||||
fqdn, _ := GetRecord(domain, keyAuth)
|
info := GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
authZone, err := FindZoneByFqdn(fqdn)
|
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("manual: could not find zone: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
||||||
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, "...")
|
fmt.Printf(dnsTemplate+"\n", info.EffectiveFQDN, DefaultTTL, "...")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
24
vendor/github.com/go-acme/lego/v4/challenge/dns01/domain.go
generated
vendored
Normal file
24
vendor/github.com/go-acme/lego/v4/challenge/dns01/domain.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package dns01
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtractSubDomain extracts the subdomain part from a domain and a zone.
|
||||||
|
func ExtractSubDomain(domain, zone string) (string, error) {
|
||||||
|
canonDomain := dns.Fqdn(domain)
|
||||||
|
canonZone := dns.Fqdn(zone)
|
||||||
|
|
||||||
|
if canonDomain == canonZone {
|
||||||
|
return "", fmt.Errorf("no subdomain because the domain and the zone are identical: %s", canonDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dns.IsSubDomain(canonZone, canonDomain) {
|
||||||
|
return "", fmt.Errorf("%s is not a subdomain of %s", canonDomain, canonZone)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSuffix(canonDomain, "."+canonZone), nil
|
||||||
|
}
|
155
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver.go
generated
vendored
155
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver.go
generated
vendored
@ -4,6 +4,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -13,9 +16,6 @@ import (
|
|||||||
|
|
||||||
const defaultResolvConf = "/etc/resolv.conf"
|
const defaultResolvConf = "/etc/resolv.conf"
|
||||||
|
|
||||||
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
|
|
||||||
var dnsTimeout = 10 * time.Second
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fqdnSoaCache = map[string]*soaCacheEntry{}
|
fqdnSoaCache = map[string]*soaCacheEntry{}
|
||||||
muFqdnSoaCache sync.Mutex
|
muFqdnSoaCache sync.Mutex
|
||||||
@ -99,12 +99,12 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
|||||||
|
|
||||||
zone, err := FindZoneByFqdn(fqdn)
|
zone, err := FindZoneByFqdn(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not determine the zone: %w", err)
|
return nil, fmt.Errorf("could not find zone: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("NS call failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rr := range r.Answer {
|
for _, rr := range r.Answer {
|
||||||
@ -116,7 +116,8 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
|||||||
if len(authoritativeNss) > 0 {
|
if len(authoritativeNss) > 0 {
|
||||||
return authoritativeNss, nil
|
return authoritativeNss, nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("could not determine authoritative nameservers")
|
|
||||||
|
return nil, fmt.Errorf("[zone=%s] could not determine authoritative nameservers", zone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
|
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
|
||||||
@ -130,7 +131,7 @@ func FindPrimaryNsByFqdn(fqdn string) (string, error) {
|
|||||||
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
||||||
}
|
}
|
||||||
return soa.primaryNs, nil
|
return soa.primaryNs, nil
|
||||||
}
|
}
|
||||||
@ -146,7 +147,7 @@ func FindZoneByFqdn(fqdn string) (string, error) {
|
|||||||
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
||||||
}
|
}
|
||||||
return soa.zone, nil
|
return soa.zone, nil
|
||||||
}
|
}
|
||||||
@ -171,35 +172,35 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error)
|
|||||||
|
|
||||||
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||||
var err error
|
var err error
|
||||||
var in *dns.Msg
|
var r *dns.Msg
|
||||||
|
|
||||||
labelIndexes := dns.Split(fqdn)
|
labelIndexes := dns.Split(fqdn)
|
||||||
for _, index := range labelIndexes {
|
for _, index := range labelIndexes {
|
||||||
domain := fqdn[index:]
|
domain := fqdn[index:]
|
||||||
|
|
||||||
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if in == nil {
|
if r == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch in.Rcode {
|
switch r.Rcode {
|
||||||
case dns.RcodeSuccess:
|
case dns.RcodeSuccess:
|
||||||
// Check if we got a SOA RR in the answer section
|
// Check if we got a SOA RR in the answer section
|
||||||
if len(in.Answer) == 0 {
|
if len(r.Answer) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// CNAME records cannot/should not exist at the root of a zone.
|
// CNAME records cannot/should not exist at the root of a zone.
|
||||||
// So we skip a domain when a CNAME is found.
|
// So we skip a domain when a CNAME is found.
|
||||||
if dnsMsgContainsCNAME(in) {
|
if dnsMsgContainsCNAME(r) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ans := range in.Answer {
|
for _, ans := range r.Answer {
|
||||||
if soa, ok := ans.(*dns.SOA); ok {
|
if soa, ok := ans.(*dns.SOA); ok {
|
||||||
return newSoaCacheEntry(soa), nil
|
return newSoaCacheEntry(soa), nil
|
||||||
}
|
}
|
||||||
@ -208,36 +209,46 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
|||||||
// NXDOMAIN
|
// NXDOMAIN
|
||||||
default:
|
default:
|
||||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||||
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
return nil, &DNSError{Message: fmt.Sprintf("unexpected response for '%s'", domain), MsgOut: r}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
return nil, &DNSError{Message: fmt.Sprintf("could not find the start of authority for '%s'", fqdn), MsgOut: r, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
|
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
|
||||||
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||||
for _, ans := range msg.Answer {
|
return slices.ContainsFunc(msg.Answer, func(rr dns.RR) bool {
|
||||||
if _, ok := ans.(*dns.CNAME); ok {
|
_, ok := rr.(*dns.CNAME)
|
||||||
return true
|
return ok
|
||||||
}
|
})
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
||||||
m := createDNSMsg(fqdn, rtype, recursive)
|
m := createDNSMsg(fqdn, rtype, recursive)
|
||||||
|
|
||||||
var in *dns.Msg
|
if len(nameservers) == 0 {
|
||||||
|
return nil, &DNSError{Message: "empty list of nameservers"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *dns.Msg
|
||||||
var err error
|
var err error
|
||||||
|
var errAll error
|
||||||
|
|
||||||
for _, ns := range nameservers {
|
for _, ns := range nameservers {
|
||||||
in, err = sendDNSQuery(m, ns)
|
r, err = sendDNSQuery(m, ns)
|
||||||
if err == nil && len(in.Answer) > 0 {
|
if err == nil && len(r.Answer) > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errAll = errors.Join(errAll, err)
|
||||||
}
|
}
|
||||||
return in, err
|
|
||||||
|
if err != nil {
|
||||||
|
return r, errAll
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
||||||
@ -253,32 +264,84 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
||||||
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok {
|
||||||
in, _, err := udp.Exchange(m, ns)
|
|
||||||
|
|
||||||
if in != nil && in.Truncated {
|
|
||||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||||
// If the TCP request succeeds, the err will reset to nil
|
r, _, err := tcp.Exchange(m, ns)
|
||||||
in, _, err = tcp.Exchange(m, ns)
|
if err != nil {
|
||||||
|
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return in, err
|
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
||||||
}
|
r, _, err := udp.Exchange(m, ns)
|
||||||
|
|
||||||
func formatDNSError(msg *dns.Msg, err error) string {
|
if r != nil && r.Truncated {
|
||||||
var parts []string
|
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||||
|
// If the TCP request succeeds, the "err" will reset to nil
|
||||||
if msg != nil {
|
r, _, err = tcp.Exchange(m, ns)
|
||||||
parts = append(parts, dns.RcodeToString[msg.Rcode])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
parts = append(parts, err.Error())
|
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(parts) > 0 {
|
return r, nil
|
||||||
return ": " + strings.Join(parts, " ")
|
}
|
||||||
}
|
|
||||||
|
// DNSError error related to DNS calls.
|
||||||
return ""
|
type DNSError struct {
|
||||||
|
Message string
|
||||||
|
NS string
|
||||||
|
MsgIn *dns.Msg
|
||||||
|
MsgOut *dns.Msg
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSError) Error() string {
|
||||||
|
var details []string
|
||||||
|
if d.NS != "" {
|
||||||
|
details = append(details, "ns="+d.NS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.MsgIn != nil && len(d.MsgIn.Question) > 0 {
|
||||||
|
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgIn.Question)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.MsgOut != nil {
|
||||||
|
if d.MsgIn == nil || len(d.MsgIn.Question) == 0 {
|
||||||
|
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgOut.Question)))
|
||||||
|
}
|
||||||
|
|
||||||
|
details = append(details, "code="+dns.RcodeToString[d.MsgOut.Rcode])
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := "DNS error"
|
||||||
|
if d.Message != "" {
|
||||||
|
msg = d.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Err != nil {
|
||||||
|
msg += ": " + d.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(details) > 0 {
|
||||||
|
msg += " [" + strings.Join(details, ", ") + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSError) Unwrap() error {
|
||||||
|
return d.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatQuestions(questions []dns.Question) string {
|
||||||
|
var parts []string
|
||||||
|
for _, question := range questions {
|
||||||
|
parts = append(parts, strings.ReplaceAll(strings.TrimPrefix(question.String(), ";"), "\t", " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, ";")
|
||||||
}
|
}
|
||||||
|
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_unix.go
generated
vendored
Normal file
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_unix.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package dns01
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
|
||||||
|
var dnsTimeout = 10 * time.Second
|
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_windows.go
generated
vendored
Normal file
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_windows.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package dns01
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// dnsTimeout is used to override the default DNS timeout of 20 seconds.
|
||||||
|
var dnsTimeout = 20 * time.Second
|
13
vendor/github.com/go-acme/lego/v4/challenge/http01/domain_matcher.go
generated
vendored
13
vendor/github.com/go-acme/lego/v4/challenge/http01/domain_matcher.go
generated
vendored
@ -23,14 +23,17 @@ import (
|
|||||||
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
|
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
|
||||||
// The header value has a different format, so you should use forwardedMatcher
|
// The header value has a different format, so you should use forwardedMatcher
|
||||||
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
|
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
|
||||||
// https://tools.ietf.org/html/rfc7239
|
// https://www.rfc-editor.org/rfc/rfc7239.html
|
||||||
//
|
//
|
||||||
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
|
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
|
||||||
// meaning that
|
// meaning that
|
||||||
// X-Header: a
|
//
|
||||||
// X-Header: b
|
// X-Header: a
|
||||||
|
// X-Header: b
|
||||||
|
//
|
||||||
// is equal to
|
// is equal to
|
||||||
// X-Header: a, b
|
//
|
||||||
|
// X-Header: a, b
|
||||||
//
|
//
|
||||||
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
|
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
|
||||||
// have in common that they only match against the first value in such lists.
|
// have in common that they only match against the first value in such lists.
|
||||||
@ -66,7 +69,7 @@ func (m arbitraryMatcher) matches(r *http.Request, domain string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
|
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
|
||||||
// See https://tools.ietf.org/html/rfc7239 for details.
|
// See https://www.rfc-editor.org/rfc/rfc7239.html for details.
|
||||||
type forwardedMatcher struct{}
|
type forwardedMatcher struct{}
|
||||||
|
|
||||||
func (m *forwardedMatcher) name() string {
|
func (m *forwardedMatcher) name() string {
|
||||||
|
31
vendor/github.com/go-acme/lego/v4/challenge/http01/http_challenge_server.go
generated
vendored
31
vendor/github.com/go-acme/lego/v4/challenge/http01/http_challenge_server.go
generated
vendored
@ -2,9 +2,11 @@ package http01
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
@ -14,8 +16,11 @@ import (
|
|||||||
// It may be instantiated without using the NewProviderServer function if
|
// It may be instantiated without using the NewProviderServer function if
|
||||||
// you want only to use the default values.
|
// you want only to use the default values.
|
||||||
type ProviderServer struct {
|
type ProviderServer struct {
|
||||||
iface string
|
address string
|
||||||
port string
|
network string // must be valid argument to net.Listen
|
||||||
|
|
||||||
|
socketMode fs.FileMode
|
||||||
|
|
||||||
matcher domainMatcher
|
matcher domainMatcher
|
||||||
done chan bool
|
done chan bool
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
@ -29,24 +34,34 @@ func NewProviderServer(iface, port string) *ProviderServer {
|
|||||||
port = "80"
|
port = "80"
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProviderServer{iface: iface, port: port, matcher: &hostMatcher{}}
|
return &ProviderServer{network: "tcp", address: net.JoinHostPort(iface, port), matcher: &hostMatcher{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnixProviderServer(socketPath string, mode fs.FileMode) *ProviderServer {
|
||||||
|
return &ProviderServer{network: "unix", address: socketPath, socketMode: mode, matcher: &hostMatcher{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
|
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
|
||||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
var err error
|
var err error
|
||||||
s.listener, err = net.Listen("tcp", s.GetAddress())
|
s.listener, err = net.Listen(s.network, s.GetAddress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
|
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.network == "unix" {
|
||||||
|
if err = os.Chmod(s.address, s.socketMode); err != nil {
|
||||||
|
return fmt.Errorf("chmod %s: %w", s.address, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.done = make(chan bool)
|
s.done = make(chan bool)
|
||||||
go s.serve(domain, token, keyAuth)
|
go s.serve(domain, token, keyAuth)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProviderServer) GetAddress() string {
|
func (s *ProviderServer) GetAddress() string {
|
||||||
return net.JoinHostPort(s.iface, s.port)
|
return s.address
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`.
|
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`.
|
||||||
@ -69,7 +84,7 @@ func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
|||||||
//
|
//
|
||||||
// The exact behavior depends on the value of headerName:
|
// The exact behavior depends on the value of headerName:
|
||||||
// - "" (the empty string) and "Host" will restore the default and only check the Host header
|
// - "" (the empty string) and "Host" will restore the default and only check the Host header
|
||||||
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://tools.ietf.org/html/rfc7239
|
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://www.rfc-editor.org/rfc/rfc7239.html
|
||||||
// - any other value will check the header value with the same name.
|
// - any other value will check the header value with the same name.
|
||||||
func (s *ProviderServer) SetProxyHeader(headerName string) {
|
func (s *ProviderServer) SetProxyHeader(headerName string) {
|
||||||
switch h := textproto.CanonicalMIMEHeaderKey(headerName); h {
|
switch h := textproto.CanonicalMIMEHeaderKey(headerName); h {
|
||||||
@ -85,7 +100,7 @@ func (s *ProviderServer) SetProxyHeader(headerName string) {
|
|||||||
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||||
path := ChallengePath(token)
|
path := ChallengePath(token)
|
||||||
|
|
||||||
// The incoming request must will be validated to prevent DNS rebind attacks.
|
// The incoming request will be validated to prevent DNS rebind attacks.
|
||||||
// We only respond with the keyAuth, when we're receiving a GET requests with
|
// We only respond with the keyAuth, when we're receiving a GET requests with
|
||||||
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
|
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@ -99,7 +114,7 @@ func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
|||||||
}
|
}
|
||||||
log.Infof("[%s] Served key authentication", domain)
|
log.Infof("[%s] Served key authentication", domain)
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
|
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure you are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
|
||||||
_, err := w.Write([]byte("TEST"))
|
_, err := w.Write([]byte("TEST"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
2
vendor/github.com/go-acme/lego/v4/challenge/resolver/errors.go
generated
vendored
2
vendor/github.com/go-acme/lego/v4/challenge/resolver/errors.go
generated
vendored
@ -19,7 +19,7 @@ func (e obtainError) Error() string {
|
|||||||
sort.Strings(domains)
|
sort.Strings(domains)
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
_, _ = fmt.Fprintf(buffer, "[%s] %s\n", domain, e[domain])
|
||||||
}
|
}
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
2
vendor/github.com/go-acme/lego/v4/challenge/resolver/prober.go
generated
vendored
2
vendor/github.com/go-acme/lego/v4/challenge/resolver/prober.go
generated
vendored
@ -128,7 +128,7 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
// For all valid preSolvers, first submit the challenges so they have max time to propagate
|
// For all valid preSolvers, first submit the challenges, so they have max time to propagate
|
||||||
for _, authSolver := range authSolvers {
|
for _, authSolver := range authSolvers {
|
||||||
authz := authSolver.authz
|
authz := authSolver.authz
|
||||||
if solvr, ok := authSolver.solver.(preSolver); ok {
|
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||||
|
13
vendor/github.com/go-acme/lego/v4/challenge/resolver/solver_manager.go
generated
vendored
13
vendor/github.com/go-acme/lego/v4/challenge/resolver/solver_manager.go
generated
vendored
@ -1,7 +1,6 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
@ -54,7 +53,7 @@ func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.Cha
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove Remove a challenge type from the available solvers.
|
// Remove removes a challenge type from the available solvers.
|
||||||
func (c *SolverManager) Remove(chlgType challenge.Type) {
|
func (c *SolverManager) Remove(chlgType challenge.Type) {
|
||||||
delete(c.solvers, chlgType)
|
delete(c.solvers, chlgType)
|
||||||
}
|
}
|
||||||
@ -107,21 +106,17 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
|||||||
bo.MaxInterval = 10 * initialInterval
|
bo.MaxInterval = 10 * initialInterval
|
||||||
bo.MaxElapsedTime = 100 * initialInterval
|
bo.MaxElapsedTime = 100 * initialInterval
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
// After the path is sent, the ACME server will access our server.
|
// After the path is sent, the ACME server will access our server.
|
||||||
// Repeatedly check the server for an updated status on our request.
|
// Repeatedly check the server for an updated status on our request.
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
return backoff.Permanent(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
valid, err := checkAuthorizationStatus(authz)
|
valid, err := checkAuthorizationStatus(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
return backoff.Permanent(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid {
|
if valid {
|
||||||
@ -132,7 +127,7 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
|||||||
return errors.New("the server didn't respond to our request")
|
return errors.New("the server didn't respond to our request")
|
||||||
}
|
}
|
||||||
|
|
||||||
return backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
return backoff.Retry(operation, bo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
||||||
|
4
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
4
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
||||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.1
|
// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-6.1
|
||||||
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||||
|
|
||||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||||
@ -83,7 +83,7 @@ func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
|
|||||||
|
|
||||||
// Add the keyAuth digest as the acmeValidation-v1 extension
|
// Add the keyAuth digest as the acmeValidation-v1 extension
|
||||||
// (marked as critical such that it won't be used by non-ACME software).
|
// (marked as critical such that it won't be used by non-ACME software).
|
||||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-3
|
// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-3
|
||||||
extensions := []pkix.Extension{
|
extensions := []pkix.Extension{
|
||||||
{
|
{
|
||||||
Id: idPeAcmeIdentifierV1,
|
Id: idPeAcmeIdentifierV1,
|
||||||
|
4
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
4
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
@ -40,7 +40,7 @@ func (s *ProviderServer) GetAddress() string {
|
|||||||
return net.JoinHostPort(s.iface, s.port)
|
return net.JoinHostPort(s.iface, s.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
|
// Present generates a certificate with an SHA-256 digest of the keyAuth provided
|
||||||
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
||||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
if s.port == "" {
|
if s.port == "" {
|
||||||
@ -61,7 +61,7 @@ func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
|||||||
|
|
||||||
// We must set that the `acme-tls/1` application level protocol is supported
|
// We must set that the `acme-tls/1` application level protocol is supported
|
||||||
// so that the protocol negotiation can succeed. Reference:
|
// so that the protocol negotiation can succeed. Reference:
|
||||||
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.2
|
// https://www.rfc-editor.org/rfc/rfc8737.html#section-6.2
|
||||||
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
||||||
|
|
||||||
// Create the listener with the created tls.Config.
|
// Create the listener with the created tls.Config.
|
||||||
|
55
vendor/github.com/go-acme/lego/v4/lego/client_config.go
generated
vendored
55
vendor/github.com/go-acme/lego/v4/lego/client_config.go
generated
vendored
@ -4,10 +4,11 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
@ -17,13 +18,18 @@ import (
|
|||||||
const (
|
const (
|
||||||
// caCertificatesEnvVar is the environment variable name that can be used to
|
// caCertificatesEnvVar is the environment variable name that can be used to
|
||||||
// specify the path to PEM encoded CA Certificates that can be used to
|
// specify the path to PEM encoded CA Certificates that can be used to
|
||||||
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
// authenticate an ACME server with an HTTPS certificate not issued by a CA in
|
||||||
// the system-wide trusted root list.
|
// the system-wide trusted root list.
|
||||||
|
// Multiple file paths can be added by using os.PathListSeparator as a separator.
|
||||||
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
|
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
|
||||||
|
|
||||||
|
// caSystemCertPool is the environment variable name that can be used to define
|
||||||
|
// if the certificates pool must use a copy of the system cert pool.
|
||||||
|
caSystemCertPool = "LEGO_CA_SYSTEM_CERT_POOL"
|
||||||
|
|
||||||
// caServerNameEnvVar is the environment variable name that can be used to
|
// caServerNameEnvVar is the environment variable name that can be used to
|
||||||
// specify the CA server name that can be used to
|
// specify the CA server name that can be used to
|
||||||
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
// authenticate an ACME server with an HTTPS certificate not issued by a CA in
|
||||||
// the system-wide trusted root list.
|
// the system-wide trusted root list.
|
||||||
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
|
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
|
||||||
|
|
||||||
@ -82,23 +88,44 @@ func createDefaultHTTPClient() *http.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initCertPool creates a *x509.CertPool populated with the PEM certificates
|
// initCertPool creates a *x509.CertPool populated with the PEM certificates
|
||||||
// found in the filepath specified in the caCertificatesEnvVar OS environment
|
// found in the filepath specified in the caCertificatesEnvVar OS environment variable.
|
||||||
// variable. If the caCertificatesEnvVar is not set then initCertPool will
|
// If the caCertificatesEnvVar is not set then initCertPool will return nil.
|
||||||
// return nil. If there is an error creating a *x509.CertPool from the provided
|
// If there is an error creating a *x509.CertPool from the provided caCertificatesEnvVar value then initCertPool will panic.
|
||||||
// caCertificatesEnvVar value then initCertPool will panic.
|
// If the caSystemCertPool is set to a "truthy value" (`1`, `t`, `T`, `TRUE`, `true`, `True`) then a copy of system cert pool will be used.
|
||||||
|
// caSystemCertPool requires caCertificatesEnvVar to be set.
|
||||||
func initCertPool() *x509.CertPool {
|
func initCertPool() *x509.CertPool {
|
||||||
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
|
customCACertsPath := os.Getenv(caCertificatesEnvVar)
|
||||||
customCAs, err := ioutil.ReadFile(customCACertsPath)
|
if customCACertsPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
certPool := getCertPool()
|
||||||
|
|
||||||
|
for _, customPath := range strings.Split(customCACertsPath, string(os.PathListSeparator)) {
|
||||||
|
customCAs, err := os.ReadFile(customPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("error reading %s=%q: %v",
|
panic(fmt.Sprintf("error reading %s=%q: %v",
|
||||||
caCertificatesEnvVar, customCACertsPath, err))
|
caCertificatesEnvVar, customPath, err))
|
||||||
}
|
}
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
|
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
|
||||||
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
|
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
|
||||||
caCertificatesEnvVar, customCACertsPath, err))
|
caCertificatesEnvVar, customPath, err))
|
||||||
}
|
}
|
||||||
return certPool
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return certPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCertPool() *x509.CertPool {
|
||||||
|
useSystemCertPool, _ := strconv.ParseBool(os.Getenv(caSystemCertPool))
|
||||||
|
if !useSystemCertPool {
|
||||||
|
return x509.NewCertPool()
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := x509.SystemCertPool()
|
||||||
|
if err == nil {
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
return x509.NewCertPool()
|
||||||
}
|
}
|
||||||
|
95
vendor/github.com/go-acme/lego/v4/platform/config/env/env.go
generated
vendored
95
vendor/github.com/go-acme/lego/v4/platform/config/env/env.go
generated
vendored
@ -3,7 +3,6 @@ package env
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -55,7 +54,6 @@ func Get(names ...string) (map[string]string, error) {
|
|||||||
// // LEGO_TWO=""
|
// // LEGO_TWO=""
|
||||||
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
|
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
|
||||||
// // => error
|
// // => error
|
||||||
//
|
|
||||||
func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
||||||
values := map[string]string{}
|
values := map[string]string{}
|
||||||
|
|
||||||
@ -80,15 +78,26 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
|||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOneWithFallback[T any](main string, defaultValue T, fn func(string) (T, error), names ...string) T {
|
||||||
|
v, _ := getOneWithFallback(main, names...)
|
||||||
|
|
||||||
|
value, err := fn(v)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
func getOneWithFallback(main string, names ...string) (string, string) {
|
func getOneWithFallback(main string, names ...string) (string, string) {
|
||||||
value := GetOrFile(main)
|
value := GetOrFile(main)
|
||||||
if len(value) > 0 {
|
if value != "" {
|
||||||
return value, main
|
return value, main
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
value := GetOrFile(name)
|
value := GetOrFile(name)
|
||||||
if len(value) > 0 {
|
if value != "" {
|
||||||
return value, main
|
return value, main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,43 +105,32 @@ func getOneWithFallback(main string, names ...string) (string, string) {
|
|||||||
return "", main
|
return "", main
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrDefaultInt returns the given environment variable value as an integer.
|
|
||||||
// Returns the default if the envvar cannot be coopered to an int, or is not found.
|
|
||||||
func GetOrDefaultInt(envVar string, defaultValue int) int {
|
|
||||||
v, err := strconv.Atoi(GetOrFile(envVar))
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrDefaultSecond returns the given environment variable value as an time.Duration (second).
|
|
||||||
// Returns the default if the envvar cannot be coopered to an int, or is not found.
|
|
||||||
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
|
|
||||||
v := GetOrDefaultInt(envVar, -1)
|
|
||||||
if v < 0 {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(v) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrDefaultString returns the given environment variable value as a string.
|
// GetOrDefaultString returns the given environment variable value as a string.
|
||||||
// Returns the default if the envvar cannot be find.
|
// Returns the default if the env var cannot be found.
|
||||||
func GetOrDefaultString(envVar, defaultValue string) string {
|
func GetOrDefaultString(envVar string, defaultValue string) string {
|
||||||
v := GetOrFile(envVar)
|
return getOrDefault(envVar, defaultValue, ParseString)
|
||||||
if v == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrDefaultBool returns the given environment variable value as a boolean.
|
// GetOrDefaultBool returns the given environment variable value as a boolean.
|
||||||
// Returns the default if the envvar cannot be coopered to a boolean, or is not found.
|
// Returns the default if the env var cannot be coopered to a boolean, or is not found.
|
||||||
func GetOrDefaultBool(envVar string, defaultValue bool) bool {
|
func GetOrDefaultBool(envVar string, defaultValue bool) bool {
|
||||||
v, err := strconv.ParseBool(GetOrFile(envVar))
|
return getOrDefault(envVar, defaultValue, strconv.ParseBool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrDefaultInt returns the given environment variable value as an integer.
|
||||||
|
// Returns the default if the env var cannot be coopered to an int, or is not found.
|
||||||
|
func GetOrDefaultInt(envVar string, defaultValue int) int {
|
||||||
|
return getOrDefault(envVar, defaultValue, strconv.Atoi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrDefaultSecond returns the given environment variable value as a time.Duration (second).
|
||||||
|
// Returns the default if the env var cannot be coopered to an int, or is not found.
|
||||||
|
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
|
||||||
|
return getOrDefault(envVar, defaultValue, ParseSecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrDefault[T any](envVar string, defaultValue T, fn func(string) (T, error)) T {
|
||||||
|
v, err := fn(GetOrFile(envVar))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
@ -155,7 +153,7 @@ func GetOrFile(envVar string) string {
|
|||||||
return envVarValue
|
return envVarValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fileContents, err := ioutil.ReadFile(fileVarValue)
|
fileContents, err := os.ReadFile(fileVarValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
|
log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
|
||||||
return ""
|
return ""
|
||||||
@ -163,3 +161,26 @@ func GetOrFile(envVar string) string {
|
|||||||
|
|
||||||
return strings.TrimSuffix(string(fileContents), "\n")
|
return strings.TrimSuffix(string(fileContents), "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseSecond parses env var value (string) to a second (time.Duration).
|
||||||
|
func ParseSecond(s string) (time.Duration, error) {
|
||||||
|
v, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v < 0 {
|
||||||
|
return 0, fmt.Errorf("unsupported value: %d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Duration(v) * time.Second, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseString parses env var value (string) to a string but throws an error when the string is empty.
|
||||||
|
func ParseString(s string) (string, error) {
|
||||||
|
if s == "" {
|
||||||
|
return "", errors.New("empty string")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
5
vendor/github.com/go-acme/lego/v4/platform/wait/wait.go
generated
vendored
5
vendor/github.com/go-acme/lego/v4/platform/wait/wait.go
generated
vendored
@ -1,7 +1,6 @@
|
|||||||
package wait
|
package wait
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -18,9 +17,9 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er
|
|||||||
select {
|
select {
|
||||||
case <-timeUp:
|
case <-timeUp:
|
||||||
if lastErr == nil {
|
if lastErr == nil {
|
||||||
return errors.New("time limit exceeded")
|
return fmt.Errorf("%s: time limit exceeded", msg)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("time limit exceeded: last error: %w", lastErr)
|
return fmt.Errorf("%s: time limit exceeded: last error: %w", msg, lastErr)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
133
vendor/github.com/go-acme/lego/v4/providers/dns/internal/errutils/client.go
generated
vendored
Normal file
133
vendor/github.com/go-acme/lego/v4/providers/dns/internal/errutils/client.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package errutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const legoDebugClientVerboseError = "LEGO_DEBUG_CLIENT_VERBOSE_ERROR"
|
||||||
|
|
||||||
|
// HTTPDoError uses with `(http.Client).Do` error.
|
||||||
|
type HTTPDoError struct {
|
||||||
|
req *http.Request
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPDoError creates a new HTTPDoError.
|
||||||
|
func NewHTTPDoError(req *http.Request, err error) *HTTPDoError {
|
||||||
|
return &HTTPDoError{req: req, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPDoError) Error() string {
|
||||||
|
msg := "unable to communicate with the API server:"
|
||||||
|
|
||||||
|
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
||||||
|
msg += fmt.Sprintf(" [request: %s %s]", h.req.Method, h.req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.err == nil {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg + fmt.Sprintf(" error: %v", h.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPDoError) Unwrap() error {
|
||||||
|
return h.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResponseError use with `io.ReadAll` when reading response body.
|
||||||
|
type ReadResponseError struct {
|
||||||
|
req *http.Request
|
||||||
|
StatusCode int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReadResponseError creates a new ReadResponseError.
|
||||||
|
func NewReadResponseError(req *http.Request, statusCode int, err error) *ReadResponseError {
|
||||||
|
return &ReadResponseError{req: req, StatusCode: statusCode, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ReadResponseError) Error() string {
|
||||||
|
msg := "unable to read response body:"
|
||||||
|
|
||||||
|
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
||||||
|
msg += fmt.Sprintf(" [request: %s %s]", r.req.Method, r.req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg += fmt.Sprintf(" [status code: %d]", r.StatusCode)
|
||||||
|
|
||||||
|
if r.err == nil {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg + fmt.Sprintf(" error: %v", r.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ReadResponseError) Unwrap() error {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalError uses with `json.Unmarshal` or `xml.Unmarshal` when reading response body.
|
||||||
|
type UnmarshalError struct {
|
||||||
|
req *http.Request
|
||||||
|
StatusCode int
|
||||||
|
Body []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnmarshalError creates a new UnmarshalError.
|
||||||
|
func NewUnmarshalError(req *http.Request, statusCode int, body []byte, err error) *UnmarshalError {
|
||||||
|
return &UnmarshalError{req: req, StatusCode: statusCode, Body: bytes.TrimSpace(body), err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UnmarshalError) Error() string {
|
||||||
|
msg := "unable to unmarshal response:"
|
||||||
|
|
||||||
|
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
||||||
|
msg += fmt.Sprintf(" [request: %s %s]", u.req.Method, u.req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg += fmt.Sprintf(" [status code: %d] body: %s", u.StatusCode, string(u.Body))
|
||||||
|
|
||||||
|
if u.err == nil {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg + fmt.Sprintf(" error: %v", u.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UnmarshalError) Unwrap() error {
|
||||||
|
return u.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexpectedStatusCodeError use when the status of the response is unexpected but there is no API error type.
|
||||||
|
type UnexpectedStatusCodeError struct {
|
||||||
|
req *http.Request
|
||||||
|
StatusCode int
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnexpectedStatusCodeError creates a new UnexpectedStatusCodeError.
|
||||||
|
func NewUnexpectedStatusCodeError(req *http.Request, statusCode int, body []byte) *UnexpectedStatusCodeError {
|
||||||
|
return &UnexpectedStatusCodeError{req: req, StatusCode: statusCode, Body: bytes.TrimSpace(body)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnexpectedResponseStatusCodeError(req *http.Request, resp *http.Response) *UnexpectedStatusCodeError {
|
||||||
|
raw, _ := io.ReadAll(resp.Body)
|
||||||
|
return &UnexpectedStatusCodeError{req: req, StatusCode: resp.StatusCode, Body: bytes.TrimSpace(raw)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UnexpectedStatusCodeError) Error() string {
|
||||||
|
msg := "unexpected status code:"
|
||||||
|
|
||||||
|
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
||||||
|
msg += fmt.Sprintf(" [request: %s %s]", u.req.Method, u.req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg + fmt.Sprintf(" [status code: %d] body: %s", u.StatusCode, string(u.Body))
|
||||||
|
}
|
179
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.go
generated
vendored
179
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.go
generated
vendored
@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -15,16 +14,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// OVH API reference: https://eu.api.ovh.com/
|
// OVH API reference: https://eu.api.ovh.com/
|
||||||
// Create a Token: https://eu.api.ovh.com/createToken/
|
// Create a Token: https://eu.api.ovh.com/createToken/
|
||||||
|
// Create a OAuth2 client: https://eu.api.ovh.com/console-preview/?section=%2Fme&branch=v1#post-/me/api/oauth2/client
|
||||||
|
|
||||||
// Environment variables names.
|
// Environment variables names.
|
||||||
const (
|
const (
|
||||||
envNamespace = "OVH_"
|
envNamespace = "OVH_"
|
||||||
|
|
||||||
EnvEndpoint = envNamespace + "ENDPOINT"
|
EnvEndpoint = envNamespace + "ENDPOINT"
|
||||||
EnvApplicationKey = envNamespace + "APPLICATION_KEY"
|
|
||||||
EnvApplicationSecret = envNamespace + "APPLICATION_SECRET"
|
|
||||||
EnvConsumerKey = envNamespace + "CONSUMER_KEY"
|
|
||||||
|
|
||||||
EnvTTL = envNamespace + "TTL"
|
EnvTTL = envNamespace + "TTL"
|
||||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||||
@ -32,6 +29,19 @@ const (
|
|||||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Authenticate using application key.
|
||||||
|
const (
|
||||||
|
EnvApplicationKey = envNamespace + "APPLICATION_KEY"
|
||||||
|
EnvApplicationSecret = envNamespace + "APPLICATION_SECRET"
|
||||||
|
EnvConsumerKey = envNamespace + "CONSUMER_KEY"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authenticate using OAuth2 client.
|
||||||
|
const (
|
||||||
|
EnvClientID = envNamespace + "CLIENT_ID"
|
||||||
|
EnvClientSecret = envNamespace + "CLIENT_SECRET"
|
||||||
|
)
|
||||||
|
|
||||||
// Record a DNS record.
|
// Record a DNS record.
|
||||||
type Record struct {
|
type Record struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
@ -42,18 +52,32 @@ type Record struct {
|
|||||||
Zone string `json:"zone,omitempty"`
|
Zone string `json:"zone,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuth2Config the OAuth2 specific configuration.
|
||||||
|
type OAuth2Config struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
}
|
||||||
|
|
||||||
// Config is used to configure the creation of the DNSProvider.
|
// Config is used to configure the creation of the DNSProvider.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
APIEndpoint string
|
APIEndpoint string
|
||||||
ApplicationKey string
|
|
||||||
ApplicationSecret string
|
ApplicationKey string
|
||||||
ConsumerKey string
|
ApplicationSecret string
|
||||||
|
ConsumerKey string
|
||||||
|
|
||||||
|
OAuth2Config *OAuth2Config
|
||||||
|
|
||||||
PropagationTimeout time.Duration
|
PropagationTimeout time.Duration
|
||||||
PollingInterval time.Duration
|
PollingInterval time.Duration
|
||||||
TTL int
|
TTL int
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) hasAppKeyAuth() bool {
|
||||||
|
return c.ApplicationKey != "" || c.ApplicationSecret != "" || c.ConsumerKey != ""
|
||||||
|
}
|
||||||
|
|
||||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||||
func NewDefaultConfig() *Config {
|
func NewDefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
@ -78,17 +102,11 @@ type DNSProvider struct {
|
|||||||
// Credentials must be passed in the environment variables:
|
// Credentials must be passed in the environment variables:
|
||||||
// OVH_ENDPOINT (must be either "ovh-eu" or "ovh-ca"), OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY.
|
// OVH_ENDPOINT (must be either "ovh-eu" or "ovh-ca"), OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY.
|
||||||
func NewDNSProvider() (*DNSProvider, error) {
|
func NewDNSProvider() (*DNSProvider, error) {
|
||||||
values, err := env.Get(EnvEndpoint, EnvApplicationKey, EnvApplicationSecret, EnvConsumerKey)
|
config, err := createConfigFromEnvVars()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ovh: %w", err)
|
return nil, fmt.Errorf("ovh: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config := NewDefaultConfig()
|
|
||||||
config.APIEndpoint = values[EnvEndpoint]
|
|
||||||
config.ApplicationKey = values[EnvApplicationKey]
|
|
||||||
config.ApplicationSecret = values[EnvApplicationSecret]
|
|
||||||
config.ConsumerKey = values[EnvConsumerKey]
|
|
||||||
|
|
||||||
return NewDNSProviderConfig(config)
|
return NewDNSProviderConfig(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,16 +116,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||||||
return nil, errors.New("ovh: the configuration of the DNS provider is nil")
|
return nil, errors.New("ovh: the configuration of the DNS provider is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.APIEndpoint == "" || config.ApplicationKey == "" || config.ApplicationSecret == "" || config.ConsumerKey == "" {
|
if config.OAuth2Config != nil && config.hasAppKeyAuth() {
|
||||||
return nil, errors.New("ovh: credentials missing")
|
return nil, errors.New("ovh: can't use both authentication systems (ApplicationKey and OAuth2)")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := ovh.NewClient(
|
client, err := newClient(config)
|
||||||
config.APIEndpoint,
|
|
||||||
config.ApplicationKey,
|
|
||||||
config.ApplicationSecret,
|
|
||||||
config.ConsumerKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ovh: %w", err)
|
return nil, fmt.Errorf("ovh: %w", err)
|
||||||
}
|
}
|
||||||
@ -123,19 +136,23 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||||||
|
|
||||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
// Parse domain name
|
// Parse domain name
|
||||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", domain, err)
|
return fmt.Errorf("ovh: could not find zone for domain %q: %w", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authZone = dns01.UnFqdn(authZone)
|
authZone = dns01.UnFqdn(authZone)
|
||||||
subDomain := extractRecordName(fqdn, authZone)
|
|
||||||
|
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ovh: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone)
|
reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone)
|
||||||
reqData := Record{FieldType: "TXT", SubDomain: subDomain, Target: value, TTL: d.config.TTL}
|
reqData := Record{FieldType: "TXT", SubDomain: subDomain, Target: info.Value, TTL: d.config.TTL}
|
||||||
|
|
||||||
// Create TXT record
|
// Create TXT record
|
||||||
var respData Record
|
var respData Record
|
||||||
@ -160,19 +177,19 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters.
|
// CleanUp removes the TXT record matching the specified parameters.
|
||||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
// get the record's unique ID from when we created it
|
// get the record's unique ID from when we created it
|
||||||
d.recordIDsMu.Lock()
|
d.recordIDsMu.Lock()
|
||||||
recordID, ok := d.recordIDs[token]
|
recordID, ok := d.recordIDs[token]
|
||||||
d.recordIDsMu.Unlock()
|
d.recordIDsMu.Unlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("ovh: unknown record ID for '%s'", fqdn)
|
return fmt.Errorf("ovh: unknown record ID for '%s'", info.EffectiveFQDN)
|
||||||
}
|
}
|
||||||
|
|
||||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", domain, err)
|
return fmt.Errorf("ovh: could not find zone for domain %q: %w", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authZone = dns01.UnFqdn(authZone)
|
authZone = dns01.UnFqdn(authZone)
|
||||||
@ -205,10 +222,94 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractRecordName(fqdn, zone string) string {
|
func createConfigFromEnvVars() (*Config, error) {
|
||||||
name := dns01.UnFqdn(fqdn)
|
firstAppKeyEnvVar := findFirstValuedEnvVar(EnvApplicationKey, EnvApplicationSecret, EnvConsumerKey)
|
||||||
if idx := strings.Index(name, "."+zone); idx != -1 {
|
firstOAuth2EnvVar := findFirstValuedEnvVar(EnvClientID, EnvClientSecret)
|
||||||
return name[:idx]
|
|
||||||
|
if firstAppKeyEnvVar != "" && firstOAuth2EnvVar != "" {
|
||||||
|
return nil, fmt.Errorf("can't use both %s and %s at the same time", firstAppKeyEnvVar, firstOAuth2EnvVar)
|
||||||
}
|
}
|
||||||
return name
|
|
||||||
|
config := NewDefaultConfig()
|
||||||
|
|
||||||
|
if firstOAuth2EnvVar != "" {
|
||||||
|
values, err := env.Get(EnvEndpoint, EnvClientID, EnvClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.APIEndpoint = values[EnvEndpoint]
|
||||||
|
config.OAuth2Config = &OAuth2Config{
|
||||||
|
ClientID: values[EnvClientID],
|
||||||
|
ClientSecret: values[EnvClientSecret],
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values, err := env.Get(EnvEndpoint, EnvApplicationKey, EnvApplicationSecret, EnvConsumerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.APIEndpoint = values[EnvEndpoint]
|
||||||
|
|
||||||
|
config.ApplicationKey = values[EnvApplicationKey]
|
||||||
|
config.ApplicationSecret = values[EnvApplicationSecret]
|
||||||
|
config.ConsumerKey = values[EnvConsumerKey]
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFirstValuedEnvVar(envVars ...string) string {
|
||||||
|
for _, envVar := range envVars {
|
||||||
|
if env.GetOrFile(envVar) != "" {
|
||||||
|
return envVar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(config *Config) (*ovh.Client, error) {
|
||||||
|
if config.OAuth2Config == nil {
|
||||||
|
return newClientApplicationKey(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newClientOAuth2(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientApplicationKey(config *Config) (*ovh.Client, error) {
|
||||||
|
if config.APIEndpoint == "" || config.ApplicationKey == "" || config.ApplicationSecret == "" || config.ConsumerKey == "" {
|
||||||
|
return nil, errors.New("credentials are missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := ovh.NewClient(
|
||||||
|
config.APIEndpoint,
|
||||||
|
config.ApplicationKey,
|
||||||
|
config.ApplicationSecret,
|
||||||
|
config.ConsumerKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("new client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientOAuth2(config *Config) (*ovh.Client, error) {
|
||||||
|
if config.APIEndpoint == "" || config.OAuth2Config.ClientID == "" || config.OAuth2Config.ClientSecret == "" {
|
||||||
|
return nil, errors.New("credentials are missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := ovh.NewOAuth2Client(
|
||||||
|
config.APIEndpoint,
|
||||||
|
config.OAuth2Config.ClientID,
|
||||||
|
config.OAuth2Config.ClientSecret,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("new OAuth2 client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
}
|
}
|
||||||
|
37
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.toml
generated
vendored
37
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.toml
generated
vendored
@ -5,11 +5,20 @@ Code = "ovh"
|
|||||||
Since = "v0.4.0"
|
Since = "v0.4.0"
|
||||||
|
|
||||||
Example = '''
|
Example = '''
|
||||||
|
# Application Key authentication:
|
||||||
|
|
||||||
OVH_APPLICATION_KEY=1234567898765432 \
|
OVH_APPLICATION_KEY=1234567898765432 \
|
||||||
OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \
|
OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \
|
||||||
OVH_CONSUMER_KEY=256vfsd347245sdfg \
|
OVH_CONSUMER_KEY=256vfsd347245sdfg \
|
||||||
OVH_ENDPOINT=ovh-eu \
|
OVH_ENDPOINT=ovh-eu \
|
||||||
lego --email myemail@example.com --dns ovh --domains my.example.org run
|
lego --email you@example.com --dns ovh --domains my.example.org run
|
||||||
|
|
||||||
|
# Or OAuth2:
|
||||||
|
|
||||||
|
OVH_CLIENT_ID=yyy \
|
||||||
|
OVH_CLIENT_SECRET=xxx \
|
||||||
|
OVH_ENDPOINT=ovh-eu \
|
||||||
|
lego --email you@example.com --dns ovh --domains my.example.org run
|
||||||
'''
|
'''
|
||||||
|
|
||||||
Additional = '''
|
Additional = '''
|
||||||
@ -17,7 +26,7 @@ Additional = '''
|
|||||||
|
|
||||||
Application key and secret can be created by following the [OVH guide](https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/).
|
Application key and secret can be created by following the [OVH guide](https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/).
|
||||||
|
|
||||||
When requesting the consumer key, the following configuration can be use to define access rights:
|
When requesting the consumer key, the following configuration can be used to define access rights:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -33,14 +42,32 @@ When requesting the consumer key, the following configuration can be use to defi
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## OAuth2 Client Credentials
|
||||||
|
|
||||||
|
Another method for authentication is by using OAuth2 client credentials.
|
||||||
|
|
||||||
|
An IAM policy and service account can be created by following the [OVH guide](https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343).
|
||||||
|
|
||||||
|
Following IAM policies need to be authorized for the affected domain:
|
||||||
|
|
||||||
|
* dnsZone:apiovh:record/create
|
||||||
|
* dnsZone:apiovh:record/delete
|
||||||
|
* dnsZone:apiovh:refresh
|
||||||
|
|
||||||
|
## Important Note
|
||||||
|
|
||||||
|
Both authentication methods cannot be used at the same time.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
[Configuration]
|
[Configuration]
|
||||||
[Configuration.Credentials]
|
[Configuration.Credentials]
|
||||||
OVH_ENDPOINT = "Endpoint URL (ovh-eu or ovh-ca)"
|
OVH_ENDPOINT = "Endpoint URL (ovh-eu or ovh-ca)"
|
||||||
OVH_APPLICATION_KEY = "Application key"
|
OVH_APPLICATION_KEY = "Application key (Application Key authentication)"
|
||||||
OVH_APPLICATION_SECRET = "Application secret"
|
OVH_APPLICATION_SECRET = "Application secret (Application Key authentication)"
|
||||||
OVH_CONSUMER_KEY = "Consumer key"
|
OVH_CONSUMER_KEY = "Consumer key (Application Key authentication)"
|
||||||
|
OVH_CLIENT_ID = "Client ID (OAuth2)"
|
||||||
|
OVH_CLIENT_SECRET = "Client secret (OAuth2)"
|
||||||
[Configuration.Additional]
|
[Configuration.Additional]
|
||||||
OVH_POLLING_INTERVAL = "Time between DNS propagation check"
|
OVH_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||||
OVH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
OVH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||||
|
228
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/client.go
generated
vendored
Normal file
228
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/client.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client the PowerDNS API client.
|
||||||
|
type Client struct {
|
||||||
|
serverName string
|
||||||
|
apiKey string
|
||||||
|
|
||||||
|
apiVersion int
|
||||||
|
|
||||||
|
Host *url.URL
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new Client.
|
||||||
|
func NewClient(host *url.URL, serverName string, apiVersion int, apiKey string) *Client {
|
||||||
|
return &Client{
|
||||||
|
serverName: serverName,
|
||||||
|
apiKey: apiKey,
|
||||||
|
apiVersion: apiVersion,
|
||||||
|
Host: host,
|
||||||
|
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) APIVersion() int {
|
||||||
|
return c.apiVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetAPIVersion(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
c.apiVersion, err = c.getAPIVersion(ctx)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getAPIVersion(ctx context.Context) (int, error) {
|
||||||
|
endpoint := c.joinPath("/", "api")
|
||||||
|
|
||||||
|
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var versions []apiVersion
|
||||||
|
err = json.Unmarshal(result, &versions)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
latestVersion := 0
|
||||||
|
for _, v := range versions {
|
||||||
|
if v.Version > latestVersion {
|
||||||
|
latestVersion = v.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestVersion, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetHostedZone(ctx context.Context, authZone string) (*HostedZone, error) {
|
||||||
|
endpoint := c.joinPath("/", "servers", c.serverName, "zones", dns.Fqdn(authZone))
|
||||||
|
|
||||||
|
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var zone HostedZone
|
||||||
|
err = json.Unmarshal(result, &zone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert pre-v1 API result
|
||||||
|
if len(zone.Records) > 0 {
|
||||||
|
zone.RRSets = []RRSet{}
|
||||||
|
for _, record := range zone.Records {
|
||||||
|
set := RRSet{
|
||||||
|
Name: record.Name,
|
||||||
|
Type: record.Type,
|
||||||
|
Records: []Record{record},
|
||||||
|
}
|
||||||
|
zone.RRSets = append(zone.RRSets, set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &zone, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateRecords(ctx context.Context, zone *HostedZone, sets RRSets) error {
|
||||||
|
endpoint := c.joinPath("/", "servers", c.serverName, "zones", zone.ID)
|
||||||
|
|
||||||
|
req, err := newJSONRequest(ctx, http.MethodPatch, endpoint, sets)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Notify(ctx context.Context, zone *HostedZone) error {
|
||||||
|
if c.apiVersion < 1 || zone.Kind != "Master" && zone.Kind != "Slave" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := c.joinPath("/", "servers", c.serverName, "zones", zone.ID, "notify")
|
||||||
|
|
||||||
|
req, err := newJSONRequest(ctx, http.MethodPut, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) joinPath(elem ...string) *url.URL {
|
||||||
|
p := path.Join(elem...)
|
||||||
|
|
||||||
|
if p != "/api" && c.apiVersion > 0 && !strings.HasPrefix(p, "/api/v") {
|
||||||
|
p = path.Join("/api", "v"+strconv.Itoa(c.apiVersion), p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Host.JoinPath(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) do(req *http.Request) (json.RawMessage, error) {
|
||||||
|
req.Header.Set("X-API-Key", c.apiKey)
|
||||||
|
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errutils.NewHTTPDoError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusUnprocessableEntity && (resp.StatusCode < 200 || resp.StatusCode >= 300) {
|
||||||
|
return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg json.RawMessage
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&msg)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
// empty body
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// other error
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for PowerDNS error message
|
||||||
|
if len(msg) > 0 && msg[0] == '{' {
|
||||||
|
var errInfo apiError
|
||||||
|
err = json.Unmarshal(msg, &errInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errutils.NewUnmarshalError(req, resp.StatusCode, msg, err)
|
||||||
|
}
|
||||||
|
if errInfo.ShortMsg != "" {
|
||||||
|
return nil, fmt.Errorf("error talking to PDNS API: %w", errInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if payload != nil {
|
||||||
|
err := json.NewEncoder(buf).Encode(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request JSON body: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, strings.TrimSuffix(endpoint.String(), "/"), buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
// PowerDNS doesn't follow HTTP convention about the "Content-Type" header.
|
||||||
|
if method != http.MethodGet && method != http.MethodDelete {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
48
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/types.go
generated
vendored
Normal file
48
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/types.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
|
// pre-v1 API
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HostedZone struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
RRSets []RRSet `json:"rrsets"`
|
||||||
|
|
||||||
|
// pre-v1 API
|
||||||
|
Records []Record `json:"records"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RRSet struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
ChangeType string `json:"changetype"`
|
||||||
|
Records []Record `json:"records,omitempty"`
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RRSets struct {
|
||||||
|
RRSets []RRSet `json:"rrsets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiError struct {
|
||||||
|
ShortMsg string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiError) Error() string {
|
||||||
|
return a.ShortMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiVersion struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
}
|
228
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.go
generated
vendored
Normal file
228
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// Package pdns implements a DNS provider for solving the DNS-01 challenge using PowerDNS nameserver.
|
||||||
|
package pdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
|
"github.com/go-acme/lego/v4/log"
|
||||||
|
"github.com/go-acme/lego/v4/platform/config/env"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/pdns/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Environment variables names.
|
||||||
|
const (
|
||||||
|
envNamespace = "PDNS_"
|
||||||
|
|
||||||
|
EnvAPIKey = envNamespace + "API_KEY"
|
||||||
|
EnvAPIURL = envNamespace + "API_URL"
|
||||||
|
|
||||||
|
EnvTTL = envNamespace + "TTL"
|
||||||
|
EnvAPIVersion = envNamespace + "API_VERSION"
|
||||||
|
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||||
|
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||||
|
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||||
|
EnvServerName = envNamespace + "SERVER_NAME"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is used to configure the creation of the DNSProvider.
|
||||||
|
type Config struct {
|
||||||
|
APIKey string
|
||||||
|
Host *url.URL
|
||||||
|
ServerName string
|
||||||
|
APIVersion int
|
||||||
|
PropagationTimeout time.Duration
|
||||||
|
PollingInterval time.Duration
|
||||||
|
TTL int
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||||
|
func NewDefaultConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
ServerName: env.GetOrDefaultString(EnvServerName, "localhost"),
|
||||||
|
APIVersion: env.GetOrDefaultInt(EnvAPIVersion, 0),
|
||||||
|
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
||||||
|
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second),
|
||||||
|
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second),
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSProvider implements the challenge.Provider interface.
|
||||||
|
type DNSProvider struct {
|
||||||
|
config *Config
|
||||||
|
client *internal.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSProvider returns a DNSProvider instance configured for pdns.
|
||||||
|
// Credentials must be passed in the environment variable:
|
||||||
|
// PDNS_API_URL and PDNS_API_KEY.
|
||||||
|
func NewDNSProvider() (*DNSProvider, error) {
|
||||||
|
values, err := env.Get(EnvAPIKey, EnvAPIURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("pdns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostURL, err := url.Parse(values[EnvAPIURL])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("pdns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := NewDefaultConfig()
|
||||||
|
config.Host = hostURL
|
||||||
|
config.APIKey = values[EnvAPIKey]
|
||||||
|
|
||||||
|
return NewDNSProviderConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSProviderConfig return a DNSProvider instance configured for pdns.
|
||||||
|
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("pdns: the configuration of the DNS provider is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.APIKey == "" {
|
||||||
|
return nil, errors.New("pdns: API key missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Host == nil || config.Host.Host == "" {
|
||||||
|
return nil, errors.New("pdns: API URL missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := internal.NewClient(config.Host, config.ServerName, config.APIVersion, config.APIKey)
|
||||||
|
|
||||||
|
if config.APIVersion <= 0 {
|
||||||
|
err := client.SetAPIVersion(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("pdns: failed to get API version %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DNSProvider{config: config, client: client}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||||
|
// Adjusting here to cope with spikes in propagation times.
|
||||||
|
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
|
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||||
|
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
|
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pdns: could not find zone for domain %q: %w", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
zone, err := d.client.GetHostedZone(ctx, authZone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pdns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := info.EffectiveFQDN
|
||||||
|
if d.client.APIVersion() == 0 {
|
||||||
|
// pre-v1 API wants non-fqdn
|
||||||
|
name = dns01.UnFqdn(info.EffectiveFQDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for existing records.
|
||||||
|
existingRRSet := findTxtRecord(zone, info.EffectiveFQDN)
|
||||||
|
|
||||||
|
// merge the existing and new records
|
||||||
|
var records []internal.Record
|
||||||
|
if existingRRSet != nil {
|
||||||
|
records = existingRRSet.Records
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := internal.Record{
|
||||||
|
Content: "\"" + info.Value + "\"",
|
||||||
|
Disabled: false,
|
||||||
|
|
||||||
|
// pre-v1 API
|
||||||
|
Type: "TXT",
|
||||||
|
Name: name,
|
||||||
|
TTL: d.config.TTL,
|
||||||
|
}
|
||||||
|
|
||||||
|
rrSets := internal.RRSets{
|
||||||
|
RRSets: []internal.RRSet{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
ChangeType: "REPLACE",
|
||||||
|
Type: "TXT",
|
||||||
|
Kind: "Master",
|
||||||
|
TTL: d.config.TTL,
|
||||||
|
Records: append(records, rec),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.client.UpdateRecords(ctx, zone, rrSets)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pdns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.client.Notify(ctx, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp removes the TXT record matching the specified parameters.
|
||||||
|
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
|
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pdns: could not find zone for domain %q: %w", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
zone, err := d.client.GetHostedZone(ctx, authZone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pdns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
set := findTxtRecord(zone, info.EffectiveFQDN)
|
||||||
|
|
||||||
|
if set == nil {
|
||||||
|
return fmt.Errorf("pdns: no existing record found for %s", info.EffectiveFQDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
rrSets := internal.RRSets{
|
||||||
|
RRSets: []internal.RRSet{
|
||||||
|
{
|
||||||
|
Name: set.Name,
|
||||||
|
Type: set.Type,
|
||||||
|
ChangeType: "DELETE",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.client.UpdateRecords(ctx, zone, rrSets)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pdns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.client.Notify(ctx, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findTxtRecord(zone *internal.HostedZone, fqdn string) *internal.RRSet {
|
||||||
|
for _, set := range zone.RRSets {
|
||||||
|
if set.Type == "TXT" && (set.Name == dns01.UnFqdn(fqdn) || set.Name == fqdn) {
|
||||||
|
return &set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
37
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.toml
generated
vendored
Normal file
37
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.toml
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Name = "PowerDNS"
|
||||||
|
Description = ''''''
|
||||||
|
URL = "https://www.powerdns.com/"
|
||||||
|
Code = "pdns"
|
||||||
|
Since = "v0.4.0"
|
||||||
|
|
||||||
|
Example = '''
|
||||||
|
PDNS_API_URL=http://pdns-server:80/ \
|
||||||
|
PDNS_API_KEY=xxxx \
|
||||||
|
lego --email you@example.com --dns pdns --domains my.example.org run
|
||||||
|
'''
|
||||||
|
|
||||||
|
Additional = '''
|
||||||
|
## Information
|
||||||
|
|
||||||
|
Tested and confirmed to work with PowerDNS authoritative server 3.4.8 and 4.0.1. Refer to [PowerDNS documentation](https://doc.powerdns.com/md/httpapi/README/) instructions on how to enable the built-in API interface.
|
||||||
|
|
||||||
|
PowerDNS Notes:
|
||||||
|
- PowerDNS API does not currently support SSL, therefore you should take care to ensure that traffic between lego and the PowerDNS API is over a trusted network, VPN etc.
|
||||||
|
- In order to have the SOA serial automatically increment each time the `_acme-challenge` record is added/modified via the API, set `SOA-EDIT-API` to `INCEPTION-INCREMENT` for the zone in the `domainmetadata` table
|
||||||
|
- Some PowerDNS servers doesn't have root API endpoints enabled and API version autodetection will not work. In that case version number can be defined using `PDNS_API_VERSION`.
|
||||||
|
'''
|
||||||
|
|
||||||
|
[Configuration]
|
||||||
|
[Configuration.Credentials]
|
||||||
|
PDNS_API_KEY = "API key"
|
||||||
|
PDNS_API_URL = "API URL"
|
||||||
|
[Configuration.Additional]
|
||||||
|
PDNS_SERVER_NAME = "Name of the server in the URL, 'localhost' by default"
|
||||||
|
PDNS_API_VERSION = "Skip API version autodetection and use the provided version number."
|
||||||
|
PDNS_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||||
|
PDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||||
|
PDNS_TTL = "The TTL of the TXT record used for the DNS challenge"
|
||||||
|
PDNS_HTTP_TIMEOUT = "API request timeout"
|
||||||
|
|
||||||
|
[Links]
|
||||||
|
API = "https://doc.powerdns.com/md/httpapi/README/"
|
10
vendor/github.com/go-acme/lego/v4/registration/registar.go
generated
vendored
10
vendor/github.com/go-acme/lego/v4/registration/registar.go
generated
vendored
@ -9,9 +9,11 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const mailTo = "mailto:"
|
||||||
|
|
||||||
// Resource represents all important information about a registration
|
// Resource represents all important information about a registration
|
||||||
// of which the client needs to keep track itself.
|
// of which the client needs to keep track itself.
|
||||||
// WARNING: will be remove in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
|
// WARNING: will be removed in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Body acme.Account `json:"body,omitempty"`
|
Body acme.Account `json:"body,omitempty"`
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
@ -52,7 +54,7 @@ func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
|
|||||||
|
|
||||||
if r.user.GetEmail() != "" {
|
if r.user.GetEmail() != "" {
|
||||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
accMsg.Contact = []string{mailTo + r.user.GetEmail()}
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := r.core.Accounts.New(accMsg)
|
account, err := r.core.Accounts.New(accMsg)
|
||||||
@ -76,7 +78,7 @@ func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOption
|
|||||||
|
|
||||||
if r.user.GetEmail() != "" {
|
if r.user.GetEmail() != "" {
|
||||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
accMsg.Contact = []string{mailTo + r.user.GetEmail()}
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded)
|
account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded)
|
||||||
@ -128,7 +130,7 @@ func (r *Registrar) UpdateRegistration(options RegisterOptions) (*Resource, erro
|
|||||||
|
|
||||||
if r.user.GetEmail() != "" {
|
if r.user.GetEmail() != "" {
|
||||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
accMsg.Contact = []string{mailTo + r.user.GetEmail()}
|
||||||
}
|
}
|
||||||
|
|
||||||
accountURL := r.user.GetRegistration().URI
|
accountURL := r.user.GetRegistration().URI
|
||||||
|
2
vendor/github.com/go-jose/go-jose/v4/.gitignore
generated
vendored
Normal file
2
vendor/github.com/go-jose/go-jose/v4/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
jose-util/jose-util
|
||||||
|
jose-util.t.err
|
53
vendor/github.com/go-jose/go-jose/v4/.golangci.yml
generated
vendored
Normal file
53
vendor/github.com/go-jose/go-jose/v4/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# https://github.com/golangci/golangci-lint
|
||||||
|
|
||||||
|
run:
|
||||||
|
skip-files:
|
||||||
|
- doc_test.go
|
||||||
|
modules-download-mode: readonly
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- gochecknoglobals
|
||||||
|
- goconst
|
||||||
|
- lll
|
||||||
|
- maligned
|
||||||
|
- nakedret
|
||||||
|
- scopelint
|
||||||
|
- unparam
|
||||||
|
- funlen # added in 1.18 (requires go-jose changes before it can be enabled)
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 35
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- text: "don't use ALL_CAPS in Go names"
|
||||||
|
linters:
|
||||||
|
- golint
|
||||||
|
- text: "hardcoded credentials"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- text: "weak cryptographic primitive"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- path: json/
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- golint
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- stylecheck
|
||||||
|
- unused
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- scopelint
|
||||||
|
- path: jwk.go
|
||||||
|
linters:
|
||||||
|
- gocyclo
|
33
vendor/github.com/go-jose/go-jose/v4/.travis.yml
generated
vendored
Normal file
33
vendor/github.com/go-jose/go-jose/v4/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
go:
|
||||||
|
- "1.13.x"
|
||||||
|
- "1.14.x"
|
||||||
|
- tip
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- export PATH=$HOME/.local/bin:$PATH
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge
|
||||||
|
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0
|
||||||
|
- pip install cram --user
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=profile.cov .
|
||||||
|
- go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner
|
||||||
|
- go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher
|
||||||
|
- go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt
|
||||||
|
- go test -v ./json # no coverage for forked encoding/json package
|
||||||
|
- golangci-lint run
|
||||||
|
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util
|
||||||
|
- cd ..
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- gocovmerge *.cov */*.cov > merged.coverprofile
|
||||||
|
- goveralls -coverprofile merged.coverprofile -service=travis-ci
|
96
vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md
generated
vendored
Normal file
96
vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# v4.0.4
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Reverted "Allow unmarshalling JSONWebKeySets with unsupported key types" as a
|
||||||
|
breaking change. See #136 / #137.
|
||||||
|
|
||||||
|
# v4.0.3
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Allow unmarshalling JSONWebKeySets with unsupported key types (#130)
|
||||||
|
- Document that OpaqueKeyEncrypter can't be implemented (for now) (#129)
|
||||||
|
- Dependency updates
|
||||||
|
|
||||||
|
# v4.0.2
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Improved documentation of Verify() to note that JSONWebKeySet is a supported
|
||||||
|
argument type (#104)
|
||||||
|
- Defined exported error values for missing x5c header and unsupported elliptic
|
||||||
|
curves error cases (#117)
|
||||||
|
|
||||||
|
# v4.0.1
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- An attacker could send a JWE containing compressed data that used large
|
||||||
|
amounts of memory and CPU when decompressed by `Decrypt` or `DecryptMulti`.
|
||||||
|
Those functions now return an error if the decompressed data would exceed
|
||||||
|
250kB or 10x the compressed size (whichever is larger). Thanks to
|
||||||
|
Enze Wang@Alioth and Jianjun Chen@Zhongguancun Lab (@zer0yu and @chenjj)
|
||||||
|
for reporting.
|
||||||
|
|
||||||
|
# v4.0.0
|
||||||
|
|
||||||
|
This release makes some breaking changes in order to more thoroughly
|
||||||
|
address the vulnerabilities discussed in [Three New Attacks Against JSON Web
|
||||||
|
Tokens][1], "Sign/encrypt confusion", "Billion hash attack", and "Polyglot
|
||||||
|
token".
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Limit JWT encryption types (exclude password or public key types) (#78)
|
||||||
|
- Enforce minimum length for HMAC keys (#85)
|
||||||
|
- jwt: match any audience in a list, rather than requiring all audiences (#81)
|
||||||
|
- jwt: accept only Compact Serialization (#75)
|
||||||
|
- jws: Add expected algorithms for signatures (#74)
|
||||||
|
- Require specifying expected algorithms for ParseEncrypted,
|
||||||
|
ParseSigned, ParseDetached, jwt.ParseEncrypted, jwt.ParseSigned,
|
||||||
|
jwt.ParseSignedAndEncrypted (#69, #74)
|
||||||
|
- Usually there is a small, known set of appropriate algorithms for a program
|
||||||
|
to use and it's a mistake to allow unexpected algorithms. For instance the
|
||||||
|
"billion hash attack" relies in part on programs accepting the PBES2
|
||||||
|
encryption algorithm and doing the necessary work even if they weren't
|
||||||
|
specifically configured to allow PBES2.
|
||||||
|
- Revert "Strip padding off base64 strings" (#82)
|
||||||
|
- The specs require base64url encoding without padding.
|
||||||
|
- Minimum supported Go version is now 1.21
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- ParseSignedCompact, ParseSignedJSON, ParseEncryptedCompact, ParseEncryptedJSON.
|
||||||
|
- These allow parsing a specific serialization, as opposed to ParseSigned and
|
||||||
|
ParseEncrypted, which try to automatically detect which serialization was
|
||||||
|
provided. It's common to require a specific serialization for a specific
|
||||||
|
protocol - for instance JWT requires Compact serialization.
|
||||||
|
|
||||||
|
[1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
|
||||||
|
|
||||||
|
# v3.0.2
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- DecryptMulti: handle decompression error (#19)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- jwe/CompactSerialize: improve performance (#67)
|
||||||
|
- Increase the default number of PBKDF2 iterations to 600k (#48)
|
||||||
|
- Return the proper algorithm for ECDSA keys (#45)
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Add Thumbprint support for opaque signers (#38)
|
||||||
|
|
||||||
|
# v3.0.1
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Security issue: an attacker specifying a large "p2c" value can cause
|
||||||
|
JSONWebEncryption.Decrypt and JSONWebEncryption.DecryptMulti to consume large
|
||||||
|
amounts of CPU, causing a DoS. Thanks to Matt Schwager (@mschwager) for the
|
||||||
|
disclosure and to Tom Tervoort for originally publishing the category of attack.
|
||||||
|
https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
|
@ -9,6 +9,7 @@ sure all tests pass by running `go test`, and format your code with `go fmt`.
|
|||||||
We also recommend using `golint` and `errcheck`.
|
We also recommend using `golint` and `errcheck`.
|
||||||
|
|
||||||
Before your code can be accepted into the project you must also sign the
|
Before your code can be accepted into the project you must also sign the
|
||||||
[Individual Contributor License Agreement][1].
|
Individual Contributor License Agreement. We use [cla-assistant.io][1] and you
|
||||||
|
will be prompted to sign once a pull request is opened.
|
||||||
|
|
||||||
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
|
[1]: https://cla-assistant.io/
|
@ -1,10 +1,9 @@
|
|||||||
# Go JOSE
|
# Go JOSE
|
||||||
|
|
||||||
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
|
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
|
||||||
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
|
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
|
||||||
[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
|
[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
|
||||||
[![build](https://travis-ci.org/square/go-jose.svg?branch=v2)](https://travis-ci.org/square/go-jose)
|
[![test](https://img.shields.io/github/checks-status/go-jose/go-jose/v4)](https://github.com/go-jose/go-jose/actions)
|
||||||
[![coverage](https://coveralls.io/repos/github/square/go-jose/badge.svg?branch=v2)](https://coveralls.io/r/square/go-jose)
|
|
||||||
|
|
||||||
Package jose aims to provide an implementation of the Javascript Object Signing
|
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||||
and Encryption set of standards. This includes support for JSON Web Encryption,
|
and Encryption set of standards. This includes support for JSON Web Encryption,
|
||||||
@ -21,13 +20,13 @@ US maintained blocked list.
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The implementation follows the
|
The implementation follows the
|
||||||
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516),
|
[JSON Web Encryption](https://dx.doi.org/10.17487/RFC7516) (RFC 7516),
|
||||||
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
|
[JSON Web Signature](https://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
|
||||||
[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519).
|
[JSON Web Token](https://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications.
|
||||||
Tables of supported algorithms are shown below. The library supports both
|
Tables of supported algorithms are shown below. The library supports both
|
||||||
the compact and full serialization formats, and has optional support for
|
the compact and JWS/JWE JSON Serialization formats, and has optional support for
|
||||||
multiple recipients. It also comes with a small command-line utility
|
multiple recipients. It also comes with a small command-line utility
|
||||||
([`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util))
|
([`jose-util`](https://pkg.go.dev/github.com/go-jose/go-jose/jose-util))
|
||||||
for dealing with JOSE messages in a shell.
|
for dealing with JOSE messages in a shell.
|
||||||
|
|
||||||
**Note**: We use a forked version of the `encoding/json` package from the Go
|
**Note**: We use a forked version of the `encoding/json` package from the Go
|
||||||
@ -38,25 +37,22 @@ libraries in other languages.
|
|||||||
|
|
||||||
### Versions
|
### Versions
|
||||||
|
|
||||||
We use [gopkg.in](https://gopkg.in) for versioning.
|
[Version 4](https://github.com/go-jose/go-jose)
|
||||||
|
([branch](https://github.com/go-jose/go-jose/tree/main),
|
||||||
|
[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
|
||||||
|
|
||||||
[Version 2](https://gopkg.in/square/go-jose.v2)
|
import "github.com/go-jose/go-jose/v4"
|
||||||
([branch](https://github.com/square/go-jose/tree/v2),
|
|
||||||
[doc](https://godoc.org/gopkg.in/square/go-jose.v2)) is the current version:
|
|
||||||
|
|
||||||
import "gopkg.in/square/go-jose.v2"
|
The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
|
||||||
|
are still useable but not actively developed anymore.
|
||||||
|
|
||||||
The old `v1` branch ([go-jose.v1](https://gopkg.in/square/go-jose.v1)) will
|
Version 3, in this repo, is still receiving security fixes but not functionality
|
||||||
still receive backported bug fixes and security fixes, but otherwise
|
updates.
|
||||||
development is frozen. All new feature development takes place on the `v2`
|
|
||||||
branch. Version 2 also contains additional sub-packages such as the
|
|
||||||
[jwt](https://godoc.org/gopkg.in/square/go-jose.v2/jwt) implementation
|
|
||||||
contributed by [@shaxbee](https://github.com/shaxbee).
|
|
||||||
|
|
||||||
### Supported algorithms
|
### Supported algorithms
|
||||||
|
|
||||||
See below for a table of supported algorithms. Algorithm identifiers match
|
See below for a table of supported algorithms. Algorithm identifiers match
|
||||||
the names in the [JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
|
the names in the [JSON Web Algorithms](https://dx.doi.org/10.17487/RFC7518)
|
||||||
standard where possible. The Godoc reference has a list of constants.
|
standard where possible. The Godoc reference has a list of constants.
|
||||||
|
|
||||||
Key encryption | Algorithm identifier(s)
|
Key encryption | Algorithm identifier(s)
|
||||||
@ -84,7 +80,7 @@ standard where possible. The Godoc reference has a list of constants.
|
|||||||
Content encryption | Algorithm identifier(s)
|
Content encryption | Algorithm identifier(s)
|
||||||
:------------------------- | :------------------------------
|
:------------------------- | :------------------------------
|
||||||
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
|
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
|
||||||
AES-GCM | A128GCM, A192GCM, A256GCM
|
AES-GCM | A128GCM, A192GCM, A256GCM
|
||||||
|
|
||||||
Compression | Algorithm identifiers(s)
|
Compression | Algorithm identifiers(s)
|
||||||
:------------------------- | -------------------------------
|
:------------------------- | -------------------------------
|
||||||
@ -99,20 +95,20 @@ allows attaching a key id.
|
|||||||
|
|
||||||
Algorithm(s) | Corresponding types
|
Algorithm(s) | Corresponding types
|
||||||
:------------------------- | -------------------------------
|
:------------------------- | -------------------------------
|
||||||
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
|
RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey)
|
||||||
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
|
ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey)
|
||||||
EdDSA<sup>1</sup> | [ed25519.PublicKey](https://godoc.org/golang.org/x/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/golang.org/x/crypto/ed25519#PrivateKey)
|
EdDSA<sup>1</sup> | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey)
|
||||||
AES, HMAC | []byte
|
AES, HMAC | []byte
|
||||||
|
|
||||||
<sup>1. Only available in version 2 of the package</sup>
|
<sup>1. Only available in version 2 or later of the package</sup>
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
|
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
|
||||||
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
|
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
|
||||||
|
|
||||||
Examples can be found in the Godoc
|
Examples can be found in the Godoc
|
||||||
reference for this package. The
|
reference for this package. The
|
||||||
[`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util)
|
[`jose-util`](https://github.com/go-jose/go-jose/tree/v4/jose-util)
|
||||||
subdirectory also contains a small command-line utility which might be useful
|
subdirectory also contains a small command-line utility which might be useful
|
||||||
as an example.
|
as an example as well.
|
13
vendor/github.com/go-jose/go-jose/v4/SECURITY.md
generated
vendored
Normal file
13
vendor/github.com/go-jose/go-jose/v4/SECURITY.md
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Security Policy
|
||||||
|
This document explains how to contact the Let's Encrypt security team to report security vulnerabilities.
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ----------|
|
||||||
|
| >= v3 | ✓ |
|
||||||
|
| v2 | ✗ |
|
||||||
|
| v1 | ✗ |
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
Please see [https://letsencrypt.org/contact/#security](https://letsencrypt.org/contact/#security) for the email address to report a vulnerability. Ensure that the subject line for your report contains the word `vulnerability` and is descriptive. Your email should be acknowledged within 24 hours. If you do not receive a response within 24 hours, please follow-up again with another email.
|
@ -20,6 +20,7 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
@ -28,9 +29,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
josecipher "github.com/go-jose/go-jose/v4/cipher"
|
||||||
josecipher "gopkg.in/square/go-jose.v2/cipher"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A generic RSA-based encrypter/verifier
|
// A generic RSA-based encrypter/verifier
|
||||||
@ -285,6 +285,9 @@ func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm
|
|||||||
|
|
||||||
switch alg {
|
switch alg {
|
||||||
case RS256, RS384, RS512:
|
case RS256, RS384, RS512:
|
||||||
|
// TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the
|
||||||
|
// random parameter is legacy and ignored, and it can be nil.
|
||||||
|
// https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1
|
||||||
out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed)
|
out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed)
|
||||||
case PS256, PS384, PS512:
|
case PS256, PS384, PS512:
|
||||||
out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
|
out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
|
||||||
@ -413,28 +416,28 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
|||||||
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
epk, err := headers.getEPK()
|
epk, err := headers.getEPK()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("square/go-jose: invalid epk header")
|
return nil, errors.New("go-jose/go-jose: invalid epk header")
|
||||||
}
|
}
|
||||||
if epk == nil {
|
if epk == nil {
|
||||||
return nil, errors.New("square/go-jose: missing epk header")
|
return nil, errors.New("go-jose/go-jose: missing epk header")
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKey, ok := epk.Key.(*ecdsa.PublicKey)
|
publicKey, ok := epk.Key.(*ecdsa.PublicKey)
|
||||||
if publicKey == nil || !ok {
|
if publicKey == nil || !ok {
|
||||||
return nil, errors.New("square/go-jose: invalid epk header")
|
return nil, errors.New("go-jose/go-jose: invalid epk header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
|
if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
|
||||||
return nil, errors.New("square/go-jose: invalid public key in epk header")
|
return nil, errors.New("go-jose/go-jose: invalid public key in epk header")
|
||||||
}
|
}
|
||||||
|
|
||||||
apuData, err := headers.getAPU()
|
apuData, err := headers.getAPU()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("square/go-jose: invalid apu header")
|
return nil, errors.New("go-jose/go-jose: invalid apu header")
|
||||||
}
|
}
|
||||||
apvData, err := headers.getAPV()
|
apvData, err := headers.getAPV()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("square/go-jose: invalid apv header")
|
return nil, errors.New("go-jose/go-jose: invalid apv header")
|
||||||
}
|
}
|
||||||
|
|
||||||
deriveKey := func(algID string, size int) []byte {
|
deriveKey := func(algID string, size int) []byte {
|
||||||
@ -489,7 +492,7 @@ func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
|
|||||||
}
|
}
|
||||||
ok := ed25519.Verify(ctx.publicKey, payload, signature)
|
ok := ed25519.Verify(ctx.publicKey, payload, signature)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("square/go-jose: ed25519 signature failed to verify")
|
return errors.New("go-jose/go-jose: ed25519 signature failed to verify")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -513,7 +516,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
|
|||||||
|
|
||||||
curveBits := ctx.privateKey.Curve.Params().BitSize
|
curveBits := ctx.privateKey.Curve.Params().BitSize
|
||||||
if expectedBitSize != curveBits {
|
if expectedBitSize != curveBits {
|
||||||
return Signature{}, fmt.Errorf("square/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
|
return Signature{}, fmt.Errorf("go-jose/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasher := hash.New()
|
hasher := hash.New()
|
||||||
@ -571,7 +574,7 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(signature) != 2*keySize {
|
if len(signature) != 2*keySize {
|
||||||
return fmt.Errorf("square/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
|
return fmt.Errorf("go-jose/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasher := hash.New()
|
hasher := hash.New()
|
||||||
@ -585,7 +588,7 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
|
|||||||
|
|
||||||
match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
|
match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
|
||||||
if !match {
|
if !match {
|
||||||
return errors.New("square/go-jose: ecdsa signature failed to verify")
|
return errors.New("go-jose/go-jose: ecdsa signature failed to verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
@ -101,23 +101,23 @@ func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
|
|||||||
// Open decrypts and authenticates the ciphertext.
|
// Open decrypts and authenticates the ciphertext.
|
||||||
func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||||
if len(ciphertext) < ctx.authtagBytes {
|
if len(ciphertext) < ctx.authtagBytes {
|
||||||
return nil, errors.New("square/go-jose: invalid ciphertext (too short)")
|
return nil, errors.New("go-jose/go-jose: invalid ciphertext (too short)")
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := len(ciphertext) - ctx.authtagBytes
|
offset := len(ciphertext) - ctx.authtagBytes
|
||||||
expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
|
expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
|
||||||
match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
|
match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
|
||||||
if match != 1 {
|
if match != 1 {
|
||||||
return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)")
|
return nil, errors.New("go-jose/go-jose: invalid ciphertext (auth tag mismatch)")
|
||||||
}
|
}
|
||||||
|
|
||||||
cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
|
cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
|
||||||
|
|
||||||
// Make copy of ciphertext buffer, don't want to modify in place
|
// Make copy of ciphertext buffer, don't want to modify in place
|
||||||
buffer := append([]byte{}, []byte(ciphertext[:offset])...)
|
buffer := append([]byte{}, ciphertext[:offset]...)
|
||||||
|
|
||||||
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
|
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
|
||||||
return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)")
|
return nil, errors.New("go-jose/go-jose: invalid ciphertext (invalid length)")
|
||||||
}
|
}
|
||||||
|
|
||||||
cbc.CryptBlocks(buffer, buffer)
|
cbc.CryptBlocks(buffer, buffer)
|
||||||
@ -177,19 +177,19 @@ func padBuffer(buffer []byte, blockSize int) []byte {
|
|||||||
// Remove padding
|
// Remove padding
|
||||||
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
|
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
|
||||||
if len(buffer)%blockSize != 0 {
|
if len(buffer)%blockSize != 0 {
|
||||||
return nil, errors.New("square/go-jose: invalid padding")
|
return nil, errors.New("go-jose/go-jose: invalid padding")
|
||||||
}
|
}
|
||||||
|
|
||||||
last := buffer[len(buffer)-1]
|
last := buffer[len(buffer)-1]
|
||||||
count := int(last)
|
count := int(last)
|
||||||
|
|
||||||
if count == 0 || count > blockSize || count > len(buffer) {
|
if count == 0 || count > blockSize || count > len(buffer) {
|
||||||
return nil, errors.New("square/go-jose: invalid padding")
|
return nil, errors.New("go-jose/go-jose: invalid padding")
|
||||||
}
|
}
|
||||||
|
|
||||||
padding := bytes.Repeat([]byte{last}, count)
|
padding := bytes.Repeat([]byte{last}, count)
|
||||||
if !bytes.HasSuffix(buffer, padding) {
|
if !bytes.HasSuffix(buffer, padding) {
|
||||||
return nil, errors.New("square/go-jose: invalid padding")
|
return nil, errors.New("go-jose/go-jose: invalid padding")
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer[:len(buffer)-count], nil
|
return buffer[:len(buffer)-count], nil
|
@ -28,7 +28,7 @@ var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
|
|||||||
// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
|
// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
|
||||||
func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
|
func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
|
||||||
if len(cek)%8 != 0 {
|
if len(cek)%8 != 0 {
|
||||||
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
|
return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks")
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(cek) / 8
|
n := len(cek) / 8
|
||||||
@ -51,7 +51,7 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
|
|||||||
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
buffer[i] = buffer[i] ^ tBytes[i]
|
buffer[i] ^= tBytes[i]
|
||||||
}
|
}
|
||||||
copy(r[t%n], buffer[8:])
|
copy(r[t%n], buffer[8:])
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
|
|||||||
// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
|
// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
|
||||||
func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
|
func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
|
||||||
if len(ciphertext)%8 != 0 {
|
if len(ciphertext)%8 != 0 {
|
||||||
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
|
return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks")
|
||||||
}
|
}
|
||||||
|
|
||||||
n := (len(ciphertext) / 8) - 1
|
n := (len(ciphertext) / 8) - 1
|
||||||
@ -87,7 +87,7 @@ func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
|
|||||||
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
buffer[i] = buffer[i] ^ tBytes[i]
|
buffer[i] ^= tBytes[i]
|
||||||
}
|
}
|
||||||
copy(buffer[8:], r[t%n])
|
copy(buffer[8:], r[t%n])
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
|
if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
|
||||||
return nil, errors.New("square/go-jose: failed to unwrap key")
|
return nil, errors.New("go-jose/go-jose: failed to unwrap key")
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]byte, n*8)
|
out := make([]byte, n*8)
|
@ -21,9 +21,8 @@ import (
|
|||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encrypter represents an encrypter which produces an encrypted JWE object.
|
// Encrypter represents an encrypter which produces an encrypted JWE object.
|
||||||
@ -76,14 +75,24 @@ type recipientKeyInfo struct {
|
|||||||
type EncrypterOptions struct {
|
type EncrypterOptions struct {
|
||||||
Compression CompressionAlgorithm
|
Compression CompressionAlgorithm
|
||||||
|
|
||||||
// Optional map of additional keys to be inserted into the protected header
|
// Optional map of name/value pairs to be inserted into the protected
|
||||||
// of a JWS object. Some specifications which make use of JWS like to insert
|
// header of a JWS object. Some specifications which make use of
|
||||||
// additional values here. All values must be JSON-serializable.
|
// JWS require additional values here.
|
||||||
|
//
|
||||||
|
// Values will be serialized by [json.Marshal] and must be valid inputs to
|
||||||
|
// that function.
|
||||||
|
//
|
||||||
|
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
|
||||||
ExtraHeaders map[HeaderKey]interface{}
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||||
// if necessary. It returns itself and so can be used in a fluent style.
|
// if necessary, and returns the updated EncrypterOptions.
|
||||||
|
//
|
||||||
|
// The v parameter will be serialized by [json.Marshal] and must be a valid
|
||||||
|
// input to that function.
|
||||||
|
//
|
||||||
|
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
|
||||||
func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
|
func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
|
||||||
if eo.ExtraHeaders == nil {
|
if eo.ExtraHeaders == nil {
|
||||||
eo.ExtraHeaders = map[HeaderKey]interface{}{}
|
eo.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
@ -111,7 +120,17 @@ func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
|
|||||||
// default of 100000 will be used for the count and a 128-bit random salt will
|
// default of 100000 will be used for the count and a 128-bit random salt will
|
||||||
// be generated.
|
// be generated.
|
||||||
type Recipient struct {
|
type Recipient struct {
|
||||||
Algorithm KeyAlgorithm
|
Algorithm KeyAlgorithm
|
||||||
|
// Key must have one of these types:
|
||||||
|
// - ed25519.PublicKey
|
||||||
|
// - *ecdsa.PublicKey
|
||||||
|
// - *rsa.PublicKey
|
||||||
|
// - *JSONWebKey
|
||||||
|
// - JSONWebKey
|
||||||
|
// - []byte (a symmetric key)
|
||||||
|
// - Any type that satisfies the OpaqueKeyEncrypter interface
|
||||||
|
//
|
||||||
|
// The type of Key must match the value of Algorithm.
|
||||||
Key interface{}
|
Key interface{}
|
||||||
KeyID string
|
KeyID string
|
||||||
PBES2Count int
|
PBES2Count int
|
||||||
@ -150,16 +169,17 @@ func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions)
|
|||||||
switch rcpt.Algorithm {
|
switch rcpt.Algorithm {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
// Direct encryption mode must be treated differently
|
// Direct encryption mode must be treated differently
|
||||||
if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
|
keyBytes, ok := rawKey.([]byte)
|
||||||
|
if !ok {
|
||||||
return nil, ErrUnsupportedKeyType
|
return nil, ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
if encrypter.cipher.keySize() != len(rawKey.([]byte)) {
|
if encrypter.cipher.keySize() != len(keyBytes) {
|
||||||
return nil, ErrInvalidKeySize
|
return nil, ErrInvalidKeySize
|
||||||
}
|
}
|
||||||
encrypter.keyGenerator = staticKeyGenerator{
|
encrypter.keyGenerator = staticKeyGenerator{
|
||||||
key: rawKey.([]byte),
|
key: keyBytes,
|
||||||
}
|
}
|
||||||
recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, rawKey.([]byte))
|
recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, keyBytes)
|
||||||
recipientInfo.keyID = keyID
|
recipientInfo.keyID = keyID
|
||||||
if rcpt.KeyID != "" {
|
if rcpt.KeyID != "" {
|
||||||
recipientInfo.keyID = rcpt.KeyID
|
recipientInfo.keyID = rcpt.KeyID
|
||||||
@ -168,16 +188,16 @@ func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions)
|
|||||||
return encrypter, nil
|
return encrypter, nil
|
||||||
case ECDH_ES:
|
case ECDH_ES:
|
||||||
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
|
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
|
||||||
typeOf := reflect.TypeOf(rawKey)
|
keyDSA, ok := rawKey.(*ecdsa.PublicKey)
|
||||||
if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) {
|
if !ok {
|
||||||
return nil, ErrUnsupportedKeyType
|
return nil, ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
encrypter.keyGenerator = ecKeyGenerator{
|
encrypter.keyGenerator = ecKeyGenerator{
|
||||||
size: encrypter.cipher.keySize(),
|
size: encrypter.cipher.keySize(),
|
||||||
algID: string(enc),
|
algID: string(enc),
|
||||||
publicKey: rawKey.(*ecdsa.PublicKey),
|
publicKey: keyDSA,
|
||||||
}
|
}
|
||||||
recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, rawKey.(*ecdsa.PublicKey))
|
recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, keyDSA)
|
||||||
recipientInfo.keyID = keyID
|
recipientInfo.keyID = keyID
|
||||||
if rcpt.KeyID != "" {
|
if rcpt.KeyID != "" {
|
||||||
recipientInfo.keyID = rcpt.KeyID
|
recipientInfo.keyID = rcpt.KeyID
|
||||||
@ -201,8 +221,8 @@ func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *Encrypter
|
|||||||
if cipher == nil {
|
if cipher == nil {
|
||||||
return nil, ErrUnsupportedAlgorithm
|
return nil, ErrUnsupportedAlgorithm
|
||||||
}
|
}
|
||||||
if rcpts == nil || len(rcpts) == 0 {
|
if len(rcpts) == 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: recipients is nil or empty")
|
return nil, fmt.Errorf("go-jose/go-jose: recipients is nil or empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypter := &genericEncrypter{
|
encrypter := &genericEncrypter{
|
||||||
@ -234,7 +254,7 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
|
|||||||
|
|
||||||
switch recipient.Algorithm {
|
switch recipient.Algorithm {
|
||||||
case DIRECT, ECDH_ES:
|
case DIRECT, ECDH_ES:
|
||||||
return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
|
return fmt.Errorf("go-jose/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
|
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
|
||||||
@ -270,9 +290,8 @@ func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKey
|
|||||||
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
|
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
|
||||||
recipient.keyID = encryptionKey.KeyID
|
recipient.keyID = encryptionKey.KeyID
|
||||||
return recipient, err
|
return recipient, err
|
||||||
}
|
case OpaqueKeyEncrypter:
|
||||||
if encrypter, ok := encryptionKey.(OpaqueKeyEncrypter); ok {
|
return newOpaqueKeyEncrypter(alg, encryptionKey)
|
||||||
return newOpaqueKeyEncrypter(alg, encrypter)
|
|
||||||
}
|
}
|
||||||
return recipientKeyInfo{}, ErrUnsupportedKeyType
|
return recipientKeyInfo{}, ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
@ -300,11 +319,11 @@ func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
|
|||||||
return newDecrypter(decryptionKey.Key)
|
return newDecrypter(decryptionKey.Key)
|
||||||
case *JSONWebKey:
|
case *JSONWebKey:
|
||||||
return newDecrypter(decryptionKey.Key)
|
return newDecrypter(decryptionKey.Key)
|
||||||
|
case OpaqueKeyDecrypter:
|
||||||
|
return &opaqueKeyDecrypter{decrypter: decryptionKey}, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
if okd, ok := decryptionKey.(OpaqueKeyDecrypter); ok {
|
|
||||||
return &opaqueKeyDecrypter{decrypter: okd}, nil
|
|
||||||
}
|
|
||||||
return nil, ErrUnsupportedKeyType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation of encrypt method producing a JWE object.
|
// Implementation of encrypt method producing a JWE object.
|
||||||
@ -326,7 +345,7 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWe
|
|||||||
obj.recipients = make([]recipientInfo, len(ctx.recipients))
|
obj.recipients = make([]recipientInfo, len(ctx.recipients))
|
||||||
|
|
||||||
if len(ctx.recipients) == 0 {
|
if len(ctx.recipients) == 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: no recipients to encrypt to")
|
return nil, fmt.Errorf("go-jose/go-jose: no recipients to encrypt to")
|
||||||
}
|
}
|
||||||
|
|
||||||
cek, headers, err := ctx.keyGenerator.genKey()
|
cek, headers, err := ctx.keyGenerator.genKey()
|
||||||
@ -403,33 +422,55 @@ func (ctx *genericEncrypter) Options() EncrypterOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt and validate the object and return the plaintext. Note that this
|
// Decrypt and validate the object and return the plaintext. This
|
||||||
// function does not support multi-recipient, if you desire multi-recipient
|
// function does not support multi-recipient. If you desire multi-recipient
|
||||||
// decryption use DecryptMulti instead.
|
// decryption use DecryptMulti instead.
|
||||||
|
//
|
||||||
|
// The decryptionKey argument must contain a private or symmetric key
|
||||||
|
// and must have one of these types:
|
||||||
|
// - *ecdsa.PrivateKey
|
||||||
|
// - *rsa.PrivateKey
|
||||||
|
// - *JSONWebKey
|
||||||
|
// - JSONWebKey
|
||||||
|
// - *JSONWebKeySet
|
||||||
|
// - JSONWebKeySet
|
||||||
|
// - []byte (a symmetric key)
|
||||||
|
// - string (a symmetric key)
|
||||||
|
// - Any type that satisfies the OpaqueKeyDecrypter interface.
|
||||||
|
//
|
||||||
|
// Note that ed25519 is only available for signatures, not encryption, so is
|
||||||
|
// not an option here.
|
||||||
|
//
|
||||||
|
// Automatically decompresses plaintext, but returns an error if the decompressed
|
||||||
|
// data would be >250kB or >10x the size of the compressed data, whichever is larger.
|
||||||
func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
|
func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
|
||||||
headers := obj.mergedHeaders(nil)
|
headers := obj.mergedHeaders(nil)
|
||||||
|
|
||||||
if len(obj.recipients) > 1 {
|
if len(obj.recipients) > 1 {
|
||||||
return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
|
return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one")
|
||||||
}
|
}
|
||||||
|
|
||||||
critical, err := headers.getCritical()
|
critical, err := headers.getCritical()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid crit header")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(critical) > 0 {
|
if len(critical) > 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypter, err := newDecrypter(decryptionKey)
|
key, err := tryJWKS(decryptionKey, obj.Header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decrypter, err := newDecrypter(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher := getContentCipher(headers.getEncryption())
|
cipher := getContentCipher(headers.getEncryption())
|
||||||
if cipher == nil {
|
if cipher == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
|
return nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := randomKeyGenerator{
|
generator := randomKeyGenerator{
|
||||||
@ -461,28 +502,41 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
|||||||
// The "zip" header parameter may only be present in the protected header.
|
// The "zip" header parameter may only be present in the protected header.
|
||||||
if comp := obj.protected.getCompression(); comp != "" {
|
if comp := obj.protected.getCompression(); comp != "" {
|
||||||
plaintext, err = decompress(comp, plaintext)
|
plaintext, err = decompress(comp, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return plaintext, err
|
return plaintext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptMulti decrypts and validates the object and returns the plaintexts,
|
// DecryptMulti decrypts and validates the object and returns the plaintexts,
|
||||||
// with support for multiple recipients. It returns the index of the recipient
|
// with support for multiple recipients. It returns the index of the recipient
|
||||||
// for which the decryption was successful, the merged headers for that recipient,
|
// for which the decryption was successful, the merged headers for that recipient,
|
||||||
// and the plaintext.
|
// and the plaintext.
|
||||||
|
//
|
||||||
|
// The decryptionKey argument must have one of the types allowed for the
|
||||||
|
// decryptionKey argument of Decrypt().
|
||||||
|
//
|
||||||
|
// Automatically decompresses plaintext, but returns an error if the decompressed
|
||||||
|
// data would be >250kB or >3x the size of the compressed data, whichever is larger.
|
||||||
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
||||||
globalHeaders := obj.mergedHeaders(nil)
|
globalHeaders := obj.mergedHeaders(nil)
|
||||||
|
|
||||||
critical, err := globalHeaders.getCritical()
|
critical, err := globalHeaders.getCritical()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: invalid crit header")
|
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(critical) > 0 {
|
if len(critical) > 0 {
|
||||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypter, err := newDecrypter(decryptionKey)
|
key, err := tryJWKS(decryptionKey, obj.Header)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, err
|
||||||
|
}
|
||||||
|
decrypter, err := newDecrypter(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, Header{}, nil, err
|
return -1, Header{}, nil, err
|
||||||
}
|
}
|
||||||
@ -490,7 +544,7 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
|
|||||||
encryption := globalHeaders.getEncryption()
|
encryption := globalHeaders.getEncryption()
|
||||||
cipher := getContentCipher(encryption)
|
cipher := getContentCipher(encryption)
|
||||||
if cipher == nil {
|
if cipher == nil {
|
||||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(encryption))
|
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(encryption))
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := randomKeyGenerator{
|
generator := randomKeyGenerator{
|
||||||
@ -524,18 +578,21 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if plaintext == nil || err != nil {
|
if plaintext == nil {
|
||||||
return -1, Header{}, nil, ErrCryptoFailure
|
return -1, Header{}, nil, ErrCryptoFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "zip" header parameter may only be present in the protected header.
|
// The "zip" header parameter may only be present in the protected header.
|
||||||
if comp := obj.protected.getCompression(); comp != "" {
|
if comp := obj.protected.getCompression(); comp != "" {
|
||||||
plaintext, err = decompress(comp, plaintext)
|
plaintext, err = decompress(comp, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitized, err := headers.sanitized()
|
sanitized, err := headers.sanitized()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: failed to sanitize header: %v", err)
|
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to sanitize header: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return index, sanitized, plaintext, err
|
return index, sanitized, plaintext, err
|
@ -15,13 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Package jose aims to provide an implementation of the Javascript Object Signing
|
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||||
and Encryption set of standards. It implements encryption and signing based on
|
and Encryption set of standards. It implements encryption and signing based on
|
||||||
the JSON Web Encryption and JSON Web Signature standards, with optional JSON
|
the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web
|
||||||
Web Token support available in a sub-package. The library supports both the
|
Token support available in a sub-package. The library supports both the compact
|
||||||
compact and full serialization formats, and has optional support for multiple
|
and JWS/JWE JSON Serialization formats, and has optional support for multiple
|
||||||
recipients.
|
recipients.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package jose
|
package jose
|
@ -21,12 +21,13 @@ import (
|
|||||||
"compress/flate"
|
"compress/flate"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper function to serialize known-good objects.
|
// Helper function to serialize known-good objects.
|
||||||
@ -41,7 +42,7 @@ func mustSerializeJSON(value interface{}) []byte {
|
|||||||
// MarshalJSON will happily serialize it as the top-level value "null". If
|
// MarshalJSON will happily serialize it as the top-level value "null". If
|
||||||
// that value is then embedded in another operation, for instance by being
|
// that value is then embedded in another operation, for instance by being
|
||||||
// base64-encoded and fed as input to a signing algorithm
|
// base64-encoded and fed as input to a signing algorithm
|
||||||
// (https://github.com/square/go-jose/issues/22), the result will be
|
// (https://github.com/go-jose/go-jose/issues/22), the result will be
|
||||||
// incorrect. Because this method is intended for known-good objects, and a nil
|
// incorrect. Because this method is intended for known-good objects, and a nil
|
||||||
// pointer is not a known-good object, we are free to panic in this case.
|
// pointer is not a known-good object, we are free to panic in this case.
|
||||||
// Note: It's not possible to directly check whether the data pointed at by an
|
// Note: It's not possible to directly check whether the data pointed at by an
|
||||||
@ -85,7 +86,7 @@ func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compress with DEFLATE
|
// deflate compresses the input.
|
||||||
func deflate(input []byte) ([]byte, error) {
|
func deflate(input []byte) ([]byte, error) {
|
||||||
output := new(bytes.Buffer)
|
output := new(bytes.Buffer)
|
||||||
|
|
||||||
@ -97,15 +98,24 @@ func deflate(input []byte) ([]byte, error) {
|
|||||||
return output.Bytes(), err
|
return output.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decompress with DEFLATE
|
// inflate decompresses the input.
|
||||||
|
//
|
||||||
|
// Errors if the decompressed data would be >250kB or >10x the size of the
|
||||||
|
// compressed data, whichever is larger.
|
||||||
func inflate(input []byte) ([]byte, error) {
|
func inflate(input []byte) ([]byte, error) {
|
||||||
output := new(bytes.Buffer)
|
output := new(bytes.Buffer)
|
||||||
reader := flate.NewReader(bytes.NewBuffer(input))
|
reader := flate.NewReader(bytes.NewBuffer(input))
|
||||||
|
|
||||||
_, err := io.Copy(output, reader)
|
maxCompressedSize := max(250_000, 10*int64(len(input)))
|
||||||
if err != nil {
|
|
||||||
|
limit := maxCompressedSize + 1
|
||||||
|
n, err := io.CopyN(output, reader, limit)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if n == limit {
|
||||||
|
return nil, fmt.Errorf("uncompressed data would be too large (>%d bytes)", maxCompressedSize)
|
||||||
|
}
|
||||||
|
|
||||||
err = reader.Close()
|
err = reader.Close()
|
||||||
return output.Bytes(), err
|
return output.Bytes(), err
|
||||||
@ -127,7 +137,7 @@ func newBuffer(data []byte) *byteBuffer {
|
|||||||
|
|
||||||
func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
|
func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
|
||||||
if len(data) > length {
|
if len(data) > length {
|
||||||
panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
|
panic("go-jose/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
|
||||||
}
|
}
|
||||||
pad := make([]byte, length-len(data))
|
pad := make([]byte, length-len(data))
|
||||||
return newBuffer(append(pad, data...))
|
return newBuffer(append(pad, data...))
|
||||||
@ -183,3 +193,36 @@ func (b byteBuffer) bigInt() *big.Int {
|
|||||||
func (b byteBuffer) toInt() int {
|
func (b byteBuffer) toInt() int {
|
||||||
return int(b.bigInt().Int64())
|
return int(b.bigInt().Int64())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func base64EncodeLen(sl []byte) int {
|
||||||
|
return base64.RawURLEncoding.EncodedLen(len(sl))
|
||||||
|
}
|
||||||
|
|
||||||
|
func base64JoinWithDots(inputs ...[]byte) string {
|
||||||
|
if len(inputs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count of dots.
|
||||||
|
totalCount := len(inputs) - 1
|
||||||
|
|
||||||
|
for _, input := range inputs {
|
||||||
|
totalCount += base64EncodeLen(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]byte, totalCount)
|
||||||
|
startEncode := 0
|
||||||
|
for i, input := range inputs {
|
||||||
|
base64.RawURLEncoding.Encode(out[startEncode:], input)
|
||||||
|
|
||||||
|
if i == len(inputs)-1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
startEncode += base64EncodeLen(input)
|
||||||
|
out[startEncode] = '.'
|
||||||
|
startEncode++
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(out)
|
||||||
|
}
|
@ -75,14 +75,13 @@ import (
|
|||||||
//
|
//
|
||||||
// The JSON null value unmarshals into an interface, map, pointer, or slice
|
// The JSON null value unmarshals into an interface, map, pointer, or slice
|
||||||
// by setting that Go value to nil. Because null is often used in JSON to mean
|
// by setting that Go value to nil. Because null is often used in JSON to mean
|
||||||
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
|
// “not present,” unmarshaling a JSON null into any other Go type has no effect
|
||||||
// on the value and produces no error.
|
// on the value and produces no error.
|
||||||
//
|
//
|
||||||
// When unmarshaling quoted strings, invalid UTF-8 or
|
// When unmarshaling quoted strings, invalid UTF-8 or
|
||||||
// invalid UTF-16 surrogate pairs are not treated as an error.
|
// invalid UTF-16 surrogate pairs are not treated as an error.
|
||||||
// Instead, they are replaced by the Unicode replacement
|
// Instead, they are replaced by the Unicode replacement
|
||||||
// character U+FFFD.
|
// character U+FFFD.
|
||||||
//
|
|
||||||
func Unmarshal(data []byte, v interface{}) error {
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
// Check for well-formedness.
|
// Check for well-formedness.
|
||||||
// Avoids filling out half a data structure
|
// Avoids filling out half a data structure
|
@ -58,6 +58,7 @@ import (
|
|||||||
// becomes a member of the object unless
|
// becomes a member of the object unless
|
||||||
// - the field's tag is "-", or
|
// - the field's tag is "-", or
|
||||||
// - the field is empty and its tag specifies the "omitempty" option.
|
// - the field is empty and its tag specifies the "omitempty" option.
|
||||||
|
//
|
||||||
// The empty values are false, 0, any
|
// The empty values are false, 0, any
|
||||||
// nil pointer or interface value, and any array, slice, map, or string of
|
// nil pointer or interface value, and any array, slice, map, or string of
|
||||||
// length zero. The object's default key string is the struct field name
|
// length zero. The object's default key string is the struct field name
|
||||||
@ -65,28 +66,28 @@ import (
|
|||||||
// the struct field's tag value is the key name, followed by an optional comma
|
// the struct field's tag value is the key name, followed by an optional comma
|
||||||
// and options. Examples:
|
// and options. Examples:
|
||||||
//
|
//
|
||||||
// // Field is ignored by this package.
|
// // Field is ignored by this package.
|
||||||
// Field int `json:"-"`
|
// Field int `json:"-"`
|
||||||
//
|
//
|
||||||
// // Field appears in JSON as key "myName".
|
// // Field appears in JSON as key "myName".
|
||||||
// Field int `json:"myName"`
|
// Field int `json:"myName"`
|
||||||
//
|
//
|
||||||
// // Field appears in JSON as key "myName" and
|
// // Field appears in JSON as key "myName" and
|
||||||
// // the field is omitted from the object if its value is empty,
|
// // the field is omitted from the object if its value is empty,
|
||||||
// // as defined above.
|
// // as defined above.
|
||||||
// Field int `json:"myName,omitempty"`
|
// Field int `json:"myName,omitempty"`
|
||||||
//
|
//
|
||||||
// // Field appears in JSON as key "Field" (the default), but
|
// // Field appears in JSON as key "Field" (the default), but
|
||||||
// // the field is skipped if empty.
|
// // the field is skipped if empty.
|
||||||
// // Note the leading comma.
|
// // Note the leading comma.
|
||||||
// Field int `json:",omitempty"`
|
// Field int `json:",omitempty"`
|
||||||
//
|
//
|
||||||
// The "string" option signals that a field is stored as JSON inside a
|
// The "string" option signals that a field is stored as JSON inside a
|
||||||
// JSON-encoded string. It applies only to fields of string, floating point,
|
// JSON-encoded string. It applies only to fields of string, floating point,
|
||||||
// integer, or boolean types. This extra level of encoding is sometimes used
|
// integer, or boolean types. This extra level of encoding is sometimes used
|
||||||
// when communicating with JavaScript programs:
|
// when communicating with JavaScript programs:
|
||||||
//
|
//
|
||||||
// Int64String int64 `json:",string"`
|
// Int64String int64 `json:",string"`
|
||||||
//
|
//
|
||||||
// The key name will be used if it's a non-empty string consisting of
|
// The key name will be used if it's a non-empty string consisting of
|
||||||
// only Unicode letters, digits, dollar signs, percent signs, hyphens,
|
// only Unicode letters, digits, dollar signs, percent signs, hyphens,
|
||||||
@ -133,7 +134,6 @@ import (
|
|||||||
// JSON cannot represent cyclic data structures and Marshal does not
|
// JSON cannot represent cyclic data structures and Marshal does not
|
||||||
// handle them. Passing cyclic structures to Marshal will result in
|
// handle them. Passing cyclic structures to Marshal will result in
|
||||||
// an infinite recursion.
|
// an infinite recursion.
|
||||||
//
|
|
||||||
func Marshal(v interface{}) ([]byte, error) {
|
func Marshal(v interface{}) ([]byte, error) {
|
||||||
e := &encodeState{}
|
e := &encodeState{}
|
||||||
err := e.marshal(v)
|
err := e.marshal(v)
|
||||||
@ -648,7 +648,7 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ bool) {
|
|||||||
// for large buffers, avoid unnecessary extra temporary
|
// for large buffers, avoid unnecessary extra temporary
|
||||||
// buffer space.
|
// buffer space.
|
||||||
enc := base64.NewEncoder(base64.StdEncoding, e)
|
enc := base64.NewEncoder(base64.StdEncoding, e)
|
||||||
enc.Write(s)
|
_, _ = enc.Write(s)
|
||||||
enc.Close()
|
enc.Close()
|
||||||
}
|
}
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
@ -240,7 +240,6 @@ var _ Unmarshaler = (*RawMessage)(nil)
|
|||||||
// Number, for JSON numbers
|
// Number, for JSON numbers
|
||||||
// string, for JSON string literals
|
// string, for JSON string literals
|
||||||
// nil, for JSON null
|
// nil, for JSON null
|
||||||
//
|
|
||||||
type Token interface{}
|
type Token interface{}
|
||||||
|
|
||||||
const (
|
const (
|
149
vendor/gopkg.in/square/go-jose.v2/jwe.go → vendor/github.com/go-jose/go-jose/v4/jwe.go
generated
vendored
149
vendor/gopkg.in/square/go-jose.v2/jwe.go → vendor/github.com/go-jose/go-jose/v4/jwe.go
generated
vendored
@ -18,10 +18,11 @@ package jose
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
||||||
@ -86,11 +87,12 @@ func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
|||||||
func (obj JSONWebEncryption) computeAuthData() []byte {
|
func (obj JSONWebEncryption) computeAuthData() []byte {
|
||||||
var protected string
|
var protected string
|
||||||
|
|
||||||
if obj.original != nil && obj.original.Protected != nil {
|
switch {
|
||||||
|
case obj.original != nil && obj.original.Protected != nil:
|
||||||
protected = obj.original.Protected.base64()
|
protected = obj.original.Protected.base64()
|
||||||
} else if obj.protected != nil {
|
case obj.protected != nil:
|
||||||
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
||||||
} else {
|
default:
|
||||||
protected = ""
|
protected = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,29 +105,75 @@ func (obj JSONWebEncryption) computeAuthData() []byte {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEncrypted parses an encrypted message in compact or full serialization format.
|
func containsKeyAlgorithm(haystack []KeyAlgorithm, needle KeyAlgorithm) bool {
|
||||||
func ParseEncrypted(input string) (*JSONWebEncryption, error) {
|
for _, algorithm := range haystack {
|
||||||
input = stripWhitespace(input)
|
if algorithm == needle {
|
||||||
if strings.HasPrefix(input, "{") {
|
return true
|
||||||
return parseEncryptedFull(input)
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
return parseEncryptedCompact(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEncryptedFull parses a message in compact format.
|
func containsContentEncryption(haystack []ContentEncryption, needle ContentEncryption) bool {
|
||||||
func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
|
for _, algorithm := range haystack {
|
||||||
|
if algorithm == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncrypted parses an encrypted message in JWE Compact or JWE JSON Serialization.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
|
||||||
|
//
|
||||||
|
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
|
||||||
|
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
|
||||||
|
// parsed data must contain a value that is present in the corresponding parameter. That
|
||||||
|
// includes the protected and unprotected headers as well as all recipients. To accept
|
||||||
|
// multiple algorithms, pass a slice of all the algorithms you want to accept.
|
||||||
|
func ParseEncrypted(input string,
|
||||||
|
keyEncryptionAlgorithms []KeyAlgorithm,
|
||||||
|
contentEncryption []ContentEncryption,
|
||||||
|
) (*JSONWebEncryption, error) {
|
||||||
|
input = stripWhitespace(input)
|
||||||
|
if strings.HasPrefix(input, "{") {
|
||||||
|
return ParseEncryptedJSON(input, keyEncryptionAlgorithms, contentEncryption)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseEncryptedCompact(input, keyEncryptionAlgorithms, contentEncryption)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncryptedJSON parses a message in JWE JSON Serialization.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
|
||||||
|
func ParseEncryptedJSON(
|
||||||
|
input string,
|
||||||
|
keyEncryptionAlgorithms []KeyAlgorithm,
|
||||||
|
contentEncryption []ContentEncryption,
|
||||||
|
) (*JSONWebEncryption, error) {
|
||||||
var parsed rawJSONWebEncryption
|
var parsed rawJSONWebEncryption
|
||||||
err := json.Unmarshal([]byte(input), &parsed)
|
err := json.Unmarshal([]byte(input), &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed.sanitized()
|
return parsed.sanitized(keyEncryptionAlgorithms, contentEncryption)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitized produces a cleaned-up JWE object from the raw JSON.
|
// sanitized produces a cleaned-up JWE object from the raw JSON.
|
||||||
func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
func (parsed *rawJSONWebEncryption) sanitized(
|
||||||
|
keyEncryptionAlgorithms []KeyAlgorithm,
|
||||||
|
contentEncryption []ContentEncryption,
|
||||||
|
) (*JSONWebEncryption, error) {
|
||||||
|
if len(keyEncryptionAlgorithms) == 0 {
|
||||||
|
return nil, errors.New("go-jose/go-jose: no key algorithms provided")
|
||||||
|
}
|
||||||
|
if len(contentEncryption) == 0 {
|
||||||
|
return nil, errors.New("go-jose/go-jose: no content encryption algorithms provided")
|
||||||
|
}
|
||||||
|
|
||||||
obj := &JSONWebEncryption{
|
obj := &JSONWebEncryption{
|
||||||
original: parsed,
|
original: parsed,
|
||||||
unprotected: parsed.Unprotected,
|
unprotected: parsed.Unprotected,
|
||||||
@ -146,7 +194,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
|||||||
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
||||||
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
|
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
|
return nil, fmt.Errorf("go-jose/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +204,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
|||||||
mergedHeaders := obj.mergedHeaders(nil)
|
mergedHeaders := obj.mergedHeaders(nil)
|
||||||
obj.Header, err = mergedHeaders.sanitized()
|
obj.Header, err = mergedHeaders.sanitized()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
|
return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(parsed.Recipients) == 0 {
|
if len(parsed.Recipients) == 0 {
|
||||||
@ -184,10 +232,31 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, recipient := range obj.recipients {
|
for i, recipient := range obj.recipients {
|
||||||
headers := obj.mergedHeaders(&recipient)
|
headers := obj.mergedHeaders(&recipient)
|
||||||
if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
|
if headers.getAlgorithm() == "" {
|
||||||
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
|
return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "alg"`, i)
|
||||||
|
}
|
||||||
|
if headers.getEncryption() == "" {
|
||||||
|
return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "enc"`, i)
|
||||||
|
}
|
||||||
|
err := validateAlgEnc(headers, keyEncryptionAlgorithms, contentEncryption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: recipient %d: %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.protected != nil {
|
||||||
|
err := validateAlgEnc(*obj.protected, keyEncryptionAlgorithms, contentEncryption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: protected header: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obj.unprotected != nil {
|
||||||
|
err := validateAlgEnc(*obj.unprotected, keyEncryptionAlgorithms, contentEncryption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: unprotected header: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,11 +268,29 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
|||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEncryptedCompact parses a message in compact format.
|
func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncryption []ContentEncryption) error {
|
||||||
func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
|
alg := headers.getAlgorithm()
|
||||||
|
enc := headers.getEncryption()
|
||||||
|
if alg != "" && !containsKeyAlgorithm(keyAlgorithms, alg) {
|
||||||
|
return fmt.Errorf("unexpected key algorithm %q; expected %q", alg, keyAlgorithms)
|
||||||
|
}
|
||||||
|
if alg != "" && !containsContentEncryption(contentEncryption, enc) {
|
||||||
|
return fmt.Errorf("unexpected content encryption algorithm %q; expected %q", enc, contentEncryption)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncryptedCompact parses a message in JWE Compact Serialization.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
|
||||||
|
func ParseEncryptedCompact(
|
||||||
|
input string,
|
||||||
|
keyAlgorithms []KeyAlgorithm,
|
||||||
|
contentEncryption []ContentEncryption,
|
||||||
|
) (*JSONWebEncryption, error) {
|
||||||
parts := strings.Split(input, ".")
|
parts := strings.Split(input, ".")
|
||||||
if len(parts) != 5 {
|
if len(parts) != 5 {
|
||||||
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
|
return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
@ -239,7 +326,7 @@ func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
|
|||||||
Tag: newBuffer(tag),
|
Tag: newBuffer(tag),
|
||||||
}
|
}
|
||||||
|
|
||||||
return raw.sanitized()
|
return raw.sanitized(keyAlgorithms, contentEncryption)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompactSerialize serializes an object using the compact serialization format.
|
// CompactSerialize serializes an object using the compact serialization format.
|
||||||
@ -251,13 +338,13 @@ func (obj JSONWebEncryption) CompactSerialize() (string, error) {
|
|||||||
|
|
||||||
serializedProtected := mustSerializeJSON(obj.protected)
|
serializedProtected := mustSerializeJSON(obj.protected)
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return base64JoinWithDots(
|
||||||
"%s.%s.%s.%s.%s",
|
serializedProtected,
|
||||||
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
obj.recipients[0].encryptedKey,
|
||||||
base64.RawURLEncoding.EncodeToString(obj.recipients[0].encryptedKey),
|
obj.iv,
|
||||||
base64.RawURLEncoding.EncodeToString(obj.iv),
|
obj.ciphertext,
|
||||||
base64.RawURLEncoding.EncodeToString(obj.ciphertext),
|
obj.tag,
|
||||||
base64.RawURLEncoding.EncodeToString(obj.tag)), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullSerialize serializes an object using the full JSON serialization format.
|
// FullSerialize serializes an object using the full JSON serialization format.
|
163
vendor/gopkg.in/square/go-jose.v2/jwk.go → vendor/github.com/go-jose/go-jose/v4/jwk.go
generated
vendored
163
vendor/gopkg.in/square/go-jose.v2/jwk.go → vendor/github.com/go-jose/go-jose/v4/jwk.go
generated
vendored
@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
@ -34,9 +35,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
|
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
|
||||||
@ -63,14 +62,26 @@ type rawJSONWebKey struct {
|
|||||||
Qi *byteBuffer `json:"qi,omitempty"`
|
Qi *byteBuffer `json:"qi,omitempty"`
|
||||||
// Certificates
|
// Certificates
|
||||||
X5c []string `json:"x5c,omitempty"`
|
X5c []string `json:"x5c,omitempty"`
|
||||||
X5u *url.URL `json:"x5u,omitempty"`
|
X5u string `json:"x5u,omitempty"`
|
||||||
X5tSHA1 string `json:"x5t,omitempty"`
|
X5tSHA1 string `json:"x5t,omitempty"`
|
||||||
X5tSHA256 string `json:"x5t#S256,omitempty"`
|
X5tSHA256 string `json:"x5t#S256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONWebKey represents a public or private key in JWK format.
|
// JSONWebKey represents a public or private key in JWK format. It can be
|
||||||
|
// marshaled into JSON and unmarshaled from JSON.
|
||||||
type JSONWebKey struct {
|
type JSONWebKey struct {
|
||||||
// Cryptographic key, can be a symmetric or asymmetric key.
|
// Key is the Go in-memory representation of this key. It must have one
|
||||||
|
// of these types:
|
||||||
|
// - ed25519.PublicKey
|
||||||
|
// - ed25519.PrivateKey
|
||||||
|
// - *ecdsa.PublicKey
|
||||||
|
// - *ecdsa.PrivateKey
|
||||||
|
// - *rsa.PublicKey
|
||||||
|
// - *rsa.PrivateKey
|
||||||
|
// - []byte (a symmetric key)
|
||||||
|
//
|
||||||
|
// When marshaling this JSONWebKey into JSON, the "kty" header parameter
|
||||||
|
// will be automatically set based on the type of this field.
|
||||||
Key interface{}
|
Key interface{}
|
||||||
// Key identifier, parsed from `kid` header.
|
// Key identifier, parsed from `kid` header.
|
||||||
KeyID string
|
KeyID string
|
||||||
@ -110,7 +121,7 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
|||||||
case []byte:
|
case []byte:
|
||||||
raw, err = fromSymmetricKey(key)
|
raw, err = fromSymmetricKey(key)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -129,13 +140,13 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
|||||||
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
||||||
if x5tSHA1Len > 0 {
|
if x5tSHA1Len > 0 {
|
||||||
if x5tSHA1Len != sha1.Size {
|
if x5tSHA1Len != sha1.Size {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)", sha1.Size, x5tSHA1Len)
|
return nil, fmt.Errorf("go-jose/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)", sha1.Size, x5tSHA1Len)
|
||||||
}
|
}
|
||||||
raw.X5tSHA1 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA1)
|
raw.X5tSHA1 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA1)
|
||||||
}
|
}
|
||||||
if x5tSHA256Len > 0 {
|
if x5tSHA256Len > 0 {
|
||||||
if x5tSHA256Len != sha256.Size {
|
if x5tSHA256Len != sha256.Size {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)", sha256.Size, x5tSHA256Len)
|
return nil, fmt.Errorf("go-jose/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)", sha256.Size, x5tSHA256Len)
|
||||||
}
|
}
|
||||||
raw.X5tSHA256 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA256)
|
raw.X5tSHA256 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA256)
|
||||||
}
|
}
|
||||||
@ -149,14 +160,16 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
|||||||
expectedSHA256 := sha256.Sum256(k.Certificates[0].Raw)
|
expectedSHA256 := sha256.Sum256(k.Certificates[0].Raw)
|
||||||
|
|
||||||
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(k.CertificateThumbprintSHA1, expectedSHA1[:]) {
|
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(k.CertificateThumbprintSHA1, expectedSHA1[:]) {
|
||||||
return nil, errors.New("square/go-jose: invalid SHA-1 thumbprint, does not match cert chain")
|
return nil, errors.New("go-jose/go-jose: invalid SHA-1 thumbprint, does not match cert chain")
|
||||||
}
|
}
|
||||||
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(k.CertificateThumbprintSHA256, expectedSHA256[:]) {
|
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(k.CertificateThumbprintSHA256, expectedSHA256[:]) {
|
||||||
return nil, errors.New("square/go-jose: invalid or SHA-256 thumbprint, does not match cert chain")
|
return nil, errors.New("go-jose/go-jose: invalid or SHA-256 thumbprint, does not match cert chain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
raw.X5u = k.CertificatesURL
|
if k.CertificatesURL != nil {
|
||||||
|
raw.X5u = k.CertificatesURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
return json.Marshal(raw)
|
return json.Marshal(raw)
|
||||||
}
|
}
|
||||||
@ -171,7 +184,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
|
|
||||||
certs, err := parseCertificateChain(raw.X5c)
|
certs, err := parseCertificateChain(raw.X5c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("square/go-jose: failed to unmarshal x5c field: %s", err)
|
return fmt.Errorf("go-jose/go-jose: failed to unmarshal x5c field: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var key interface{}
|
var key interface{}
|
||||||
@ -211,7 +224,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
}
|
}
|
||||||
case "oct":
|
case "oct":
|
||||||
if certPub != nil {
|
if certPub != nil {
|
||||||
return errors.New("square/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
|
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
|
||||||
}
|
}
|
||||||
key, err = raw.symmetricKey()
|
key, err = raw.symmetricKey()
|
||||||
case "OKP":
|
case "OKP":
|
||||||
@ -226,10 +239,10 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
keyPub = key
|
keyPub = key
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
|
err = fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
|
err = fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -238,19 +251,24 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
|
|
||||||
if certPub != nil && keyPub != nil {
|
if certPub != nil && keyPub != nil {
|
||||||
if !reflect.DeepEqual(certPub, keyPub) {
|
if !reflect.DeepEqual(certPub, keyPub) {
|
||||||
return errors.New("square/go-jose: invalid JWK, public keys in key and x5c fields do not match")
|
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
|
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
|
||||||
|
|
||||||
k.CertificatesURL = raw.X5u
|
if raw.X5u != "" {
|
||||||
|
k.CertificatesURL, err = url.Parse(raw.X5u)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("go-jose/go-jose: invalid JWK, x5u header is invalid URL: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// x5t parameters are base64url-encoded SHA thumbprints
|
// x5t parameters are base64url-encoded SHA thumbprints
|
||||||
// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
|
// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
|
||||||
x5tSHA1bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA1)
|
x5tSHA1bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("square/go-jose: invalid JWK, x5t header has invalid encoding")
|
return errors.New("go-jose/go-jose: invalid JWK, x5t header has invalid encoding")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC 7517, Section 4.8 is ambiguous as to whether the digest output should be byte or hex,
|
// RFC 7517, Section 4.8 is ambiguous as to whether the digest output should be byte or hex,
|
||||||
@ -260,7 +278,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
if len(x5tSHA1bytes) == 2*sha1.Size {
|
if len(x5tSHA1bytes) == 2*sha1.Size {
|
||||||
hx, err := hex.DecodeString(string(x5tSHA1bytes))
|
hx, err := hex.DecodeString(string(x5tSHA1bytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
|
return fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
x5tSHA1bytes = hx
|
x5tSHA1bytes = hx
|
||||||
@ -270,13 +288,13 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
|
|
||||||
x5tSHA256bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA256)
|
x5tSHA256bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA256)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("square/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
|
return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(x5tSHA256bytes) == 2*sha256.Size {
|
if len(x5tSHA256bytes) == 2*sha256.Size {
|
||||||
hx256, err := hex.DecodeString(string(x5tSHA256bytes))
|
hx256, err := hex.DecodeString(string(x5tSHA256bytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
|
return fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
|
||||||
}
|
}
|
||||||
x5tSHA256bytes = hx256
|
x5tSHA256bytes = hx256
|
||||||
}
|
}
|
||||||
@ -286,10 +304,10 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
x5tSHA1Len := len(k.CertificateThumbprintSHA1)
|
x5tSHA1Len := len(k.CertificateThumbprintSHA1)
|
||||||
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
||||||
if x5tSHA1Len > 0 && x5tSHA1Len != sha1.Size {
|
if x5tSHA1Len > 0 && x5tSHA1Len != sha1.Size {
|
||||||
return errors.New("square/go-jose: invalid JWK, x5t header is of incorrect size")
|
return errors.New("go-jose/go-jose: invalid JWK, x5t header is of incorrect size")
|
||||||
}
|
}
|
||||||
if x5tSHA256Len > 0 && x5tSHA256Len != sha256.Size {
|
if x5tSHA256Len > 0 && x5tSHA256Len != sha256.Size {
|
||||||
return errors.New("square/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
|
return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If certificate chain *and* thumbprints are set, verify correctness.
|
// If certificate chain *and* thumbprints are set, verify correctness.
|
||||||
@ -299,11 +317,11 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
sha256sum := sha256.Sum256(leaf.Raw)
|
sha256sum := sha256.Sum256(leaf.Raw)
|
||||||
|
|
||||||
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(sha1sum[:], k.CertificateThumbprintSHA1) {
|
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(sha1sum[:], k.CertificateThumbprintSHA1) {
|
||||||
return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
|
return errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(sha256sum[:], k.CertificateThumbprintSHA256) {
|
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(sha256sum[:], k.CertificateThumbprintSHA256) {
|
||||||
return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
|
return errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +360,7 @@ func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(x.Bytes()) > coordLength || len(y.Bytes()) > coordLength {
|
if len(x.Bytes()) > coordLength || len(y.Bytes()) > coordLength {
|
||||||
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
|
return "", errors.New("go-jose/go-jose: invalid elliptic key (too large)")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(ecThumbprintTemplate, crv,
|
return fmt.Sprintf(ecThumbprintTemplate, crv,
|
||||||
@ -359,7 +377,7 @@ func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
|||||||
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
|
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
|
||||||
crv := "Ed25519"
|
crv := "Ed25519"
|
||||||
if len(ed) > 32 {
|
if len(ed) > 32 {
|
||||||
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
|
return "", errors.New("go-jose/go-jose: invalid elliptic key (too large)")
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(edThumbprintTemplate, crv,
|
return fmt.Sprintf(edThumbprintTemplate, crv,
|
||||||
newFixedSizeBuffer(ed, 32).base64()), nil
|
newFixedSizeBuffer(ed, 32).base64()), nil
|
||||||
@ -383,8 +401,10 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
|||||||
input, err = rsaThumbprintInput(key.N, key.E)
|
input, err = rsaThumbprintInput(key.N, key.E)
|
||||||
case ed25519.PrivateKey:
|
case ed25519.PrivateKey:
|
||||||
input, err = edThumbprintInput(ed25519.PublicKey(key[32:]))
|
input, err = edThumbprintInput(ed25519.PublicKey(key[32:]))
|
||||||
|
case OpaqueSigner:
|
||||||
|
return key.Public().Thumbprint(hash)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -392,7 +412,7 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h := hash.New()
|
h := hash.New()
|
||||||
h.Write([]byte(input))
|
_, _ = h.Write([]byte(input))
|
||||||
return h.Sum(nil), nil
|
return h.Sum(nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,7 +483,7 @@ func (k *JSONWebKey) Valid() bool {
|
|||||||
|
|
||||||
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
||||||
if key.N == nil || key.E == nil {
|
if key.N == nil || key.E == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, missing n/e values")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &rsa.PublicKey{
|
return &rsa.PublicKey{
|
||||||
@ -498,29 +518,29 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
|||||||
case "P-521":
|
case "P-521":
|
||||||
curve = elliptic.P521()
|
curve = elliptic.P521()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.X == nil || key.Y == nil {
|
if key.X == nil || key.Y == nil {
|
||||||
return nil, errors.New("square/go-jose: invalid EC key, missing x/y values")
|
return nil, errors.New("go-jose/go-jose: invalid EC key, missing x/y values")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The length of this octet string MUST be the full size of a coordinate for
|
// The length of this octet string MUST be the full size of a coordinate for
|
||||||
// the curve specified in the "crv" parameter.
|
// the curve specified in the "crv" parameter.
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||||
if curveSize(curve) != len(key.X.data) {
|
if curveSize(curve) != len(key.X.data) {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for x")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for x")
|
||||||
}
|
}
|
||||||
|
|
||||||
if curveSize(curve) != len(key.Y.data) {
|
if curveSize(curve) != len(key.Y.data) {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for y")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for y")
|
||||||
}
|
}
|
||||||
|
|
||||||
x := key.X.bigInt()
|
x := key.X.bigInt()
|
||||||
y := key.Y.bigInt()
|
y := key.Y.bigInt()
|
||||||
|
|
||||||
if !curve.IsOnCurve(x, y) {
|
if !curve.IsOnCurve(x, y) {
|
||||||
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
|
return nil, errors.New("go-jose/go-jose: invalid EC key, X/Y are not on declared curve")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ecdsa.PublicKey{
|
return &ecdsa.PublicKey{
|
||||||
@ -532,7 +552,7 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
|||||||
|
|
||||||
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
|
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
|
||||||
if pub == nil || pub.X == nil || pub.Y == nil {
|
if pub == nil || pub.X == nil || pub.Y == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC key (nil, or X/Y missing)")
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := curveName(pub.Curve)
|
name, err := curveName(pub.Curve)
|
||||||
@ -546,7 +566,7 @@ func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
|
|||||||
yBytes := pub.Y.Bytes()
|
yBytes := pub.Y.Bytes()
|
||||||
|
|
||||||
if len(xBytes) > size || len(yBytes) > size {
|
if len(xBytes) > size || len(yBytes) > size {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC key (X/Y too large)")
|
||||||
}
|
}
|
||||||
|
|
||||||
key := &rawJSONWebKey{
|
key := &rawJSONWebKey{
|
||||||
@ -569,7 +589,7 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(missing) > 0 {
|
if len(missing) > 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
|
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey := make([]byte, ed25519.PrivateKeySize)
|
privateKey := make([]byte, ed25519.PrivateKeySize)
|
||||||
@ -581,7 +601,7 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
|
|||||||
|
|
||||||
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
|
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
|
||||||
if key.X == nil {
|
if key.X == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid Ed key, missing x value")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed key, missing x value")
|
||||||
}
|
}
|
||||||
publicKey := make([]byte, ed25519.PublicKeySize)
|
publicKey := make([]byte, ed25519.PublicKeySize)
|
||||||
copy(publicKey[0:32], key.X.bytes())
|
copy(publicKey[0:32], key.X.bytes())
|
||||||
@ -605,7 +625,7 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(missing) > 0 {
|
if len(missing) > 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
rv := &rsa.PrivateKey{
|
rv := &rsa.PrivateKey{
|
||||||
@ -675,34 +695,34 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
|||||||
case "P-521":
|
case "P-521":
|
||||||
curve = elliptic.P521()
|
curve = elliptic.P521()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.X == nil || key.Y == nil || key.D == nil {
|
if key.X == nil || key.Y == nil || key.D == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, missing x/y/d values")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The length of this octet string MUST be the full size of a coordinate for
|
// The length of this octet string MUST be the full size of a coordinate for
|
||||||
// the curve specified in the "crv" parameter.
|
// the curve specified in the "crv" parameter.
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||||
if curveSize(curve) != len(key.X.data) {
|
if curveSize(curve) != len(key.X.data) {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for x")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for x")
|
||||||
}
|
}
|
||||||
|
|
||||||
if curveSize(curve) != len(key.Y.data) {
|
if curveSize(curve) != len(key.Y.data) {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for y")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for y")
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
||||||
if dSize(curve) != len(key.D.data) {
|
if dSize(curve) != len(key.D.data) {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for d")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for d")
|
||||||
}
|
}
|
||||||
|
|
||||||
x := key.X.bigInt()
|
x := key.X.bigInt()
|
||||||
y := key.Y.bigInt()
|
y := key.Y.bigInt()
|
||||||
|
|
||||||
if !curve.IsOnCurve(x, y) {
|
if !curve.IsOnCurve(x, y) {
|
||||||
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
|
return nil, errors.New("go-jose/go-jose: invalid EC key, X/Y are not on declared curve")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ecdsa.PrivateKey{
|
return &ecdsa.PrivateKey{
|
||||||
@ -722,7 +742,7 @@ func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJSONWebKey, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ec.D == nil {
|
if ec.D == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
raw.D = newFixedSizeBuffer(ec.D.Bytes(), dSize(ec.PublicKey.Curve))
|
raw.D = newFixedSizeBuffer(ec.D.Bytes(), dSize(ec.PublicKey.Curve))
|
||||||
@ -740,7 +760,7 @@ func dSize(curve elliptic.Curve) int {
|
|||||||
bitLen := order.BitLen()
|
bitLen := order.BitLen()
|
||||||
size := bitLen / 8
|
size := bitLen / 8
|
||||||
if bitLen%8 != 0 {
|
if bitLen%8 != 0 {
|
||||||
size = size + 1
|
size++
|
||||||
}
|
}
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
@ -754,7 +774,50 @@ func fromSymmetricKey(key []byte) (*rawJSONWebKey, error) {
|
|||||||
|
|
||||||
func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
|
func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
|
||||||
if key.K == nil {
|
if key.K == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid OCT (symmetric) key, missing k value")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid OCT (symmetric) key, missing k value")
|
||||||
}
|
}
|
||||||
return key.K.bytes(), nil
|
return key.K.bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrJWKSKidNotFound is returned when a JWKS does not contain a JWK with a
|
||||||
|
// key ID which matches one in the provided tokens headers.
|
||||||
|
ErrJWKSKidNotFound = errors.New("go-jose/go-jose: JWK with matching kid not found in JWK Set")
|
||||||
|
)
|
||||||
|
|
||||||
|
func tryJWKS(key interface{}, headers ...Header) (interface{}, error) {
|
||||||
|
var jwks JSONWebKeySet
|
||||||
|
|
||||||
|
switch jwksType := key.(type) {
|
||||||
|
case *JSONWebKeySet:
|
||||||
|
jwks = *jwksType
|
||||||
|
case JSONWebKeySet:
|
||||||
|
jwks = jwksType
|
||||||
|
default:
|
||||||
|
// If the specified key is not a JWKS, return as is.
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the KID to search for from the headers.
|
||||||
|
var kid string
|
||||||
|
for _, header := range headers {
|
||||||
|
if header.KeyID != "" {
|
||||||
|
kid = header.KeyID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no KID is specified in the headers, reject.
|
||||||
|
if kid == "" {
|
||||||
|
return nil, ErrJWKSKidNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the JWK with the matching KID. If no JWK with the specified KID is
|
||||||
|
// found, reject.
|
||||||
|
keys := jwks.Key(kid)
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil, ErrJWKSKidNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys[0].Key, nil
|
||||||
|
}
|
103
vendor/gopkg.in/square/go-jose.v2/jws.go → vendor/github.com/go-jose/go-jose/v4/jws.go
generated
vendored
103
vendor/gopkg.in/square/go-jose.v2/jws.go → vendor/github.com/go-jose/go-jose/v4/jws.go
generated
vendored
@ -23,7 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
||||||
@ -75,22 +75,41 @@ type Signature struct {
|
|||||||
original *rawSignatureInfo
|
original *rawSignatureInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSigned parses a signed message in compact or full serialization format.
|
// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization.
|
||||||
func ParseSigned(signature string) (*JSONWebSignature, error) {
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7515#section-7
|
||||||
|
func ParseSigned(
|
||||||
|
signature string,
|
||||||
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
|
) (*JSONWebSignature, error) {
|
||||||
signature = stripWhitespace(signature)
|
signature = stripWhitespace(signature)
|
||||||
if strings.HasPrefix(signature, "{") {
|
if strings.HasPrefix(signature, "{") {
|
||||||
return parseSignedFull(signature)
|
return ParseSignedJSON(signature, signatureAlgorithms)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseSignedCompact(signature, nil)
|
return parseSignedCompact(signature, nil, signatureAlgorithms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignedCompact parses a message in JWS Compact Serialization.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
|
||||||
|
func ParseSignedCompact(
|
||||||
|
signature string,
|
||||||
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
|
) (*JSONWebSignature, error) {
|
||||||
|
return parseSignedCompact(signature, nil, signatureAlgorithms)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDetached parses a signed message in compact serialization format with detached payload.
|
// ParseDetached parses a signed message in compact serialization format with detached payload.
|
||||||
func ParseDetached(signature string, payload []byte) (*JSONWebSignature, error) {
|
func ParseDetached(
|
||||||
|
signature string,
|
||||||
|
payload []byte,
|
||||||
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
|
) (*JSONWebSignature, error) {
|
||||||
if payload == nil {
|
if payload == nil {
|
||||||
return nil, errors.New("square/go-jose: nil payload")
|
return nil, errors.New("go-jose/go-jose: nil payload")
|
||||||
}
|
}
|
||||||
return parseSignedCompact(stripWhitespace(signature), payload)
|
return parseSignedCompact(stripWhitespace(signature), payload, signatureAlgorithms)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a header value
|
// Get a header value
|
||||||
@ -137,21 +156,38 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
|
|||||||
return authData.Bytes(), nil
|
return authData.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSignedFull parses a message in full format.
|
// ParseSignedJSON parses a message in JWS JSON Serialization.
|
||||||
func parseSignedFull(input string) (*JSONWebSignature, error) {
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.2
|
||||||
|
func ParseSignedJSON(
|
||||||
|
input string,
|
||||||
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
|
) (*JSONWebSignature, error) {
|
||||||
var parsed rawJSONWebSignature
|
var parsed rawJSONWebSignature
|
||||||
err := json.Unmarshal([]byte(input), &parsed)
|
err := json.Unmarshal([]byte(input), &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed.sanitized()
|
return parsed.sanitized(signatureAlgorithms)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsSignatureAlgorithm(haystack []SignatureAlgorithm, needle SignatureAlgorithm) bool {
|
||||||
|
for _, algorithm := range haystack {
|
||||||
|
if algorithm == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
||||||
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgorithm) (*JSONWebSignature, error) {
|
||||||
|
if len(signatureAlgorithms) == 0 {
|
||||||
|
return nil, errors.New("go-jose/go-jose: no signature algorithms specified")
|
||||||
|
}
|
||||||
if parsed.Payload == nil {
|
if parsed.Payload == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
|
return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := &JSONWebSignature{
|
obj := &JSONWebSignature{
|
||||||
@ -198,6 +234,12 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alg := SignatureAlgorithm(signature.Header.Algorithm)
|
||||||
|
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
|
||||||
|
alg, signatureAlgorithms)
|
||||||
|
}
|
||||||
|
|
||||||
if signature.header != nil {
|
if signature.header != nil {
|
||||||
signature.Unprotected, err = signature.header.sanitized()
|
signature.Unprotected, err = signature.header.sanitized()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -215,7 +257,7 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
|||||||
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||||
jwk := signature.Header.JSONWebKey
|
jwk := signature.Header.JSONWebKey
|
||||||
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||||
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
return nil, errors.New("go-jose/go-jose: invalid embedded jwk, must be public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.Signatures = append(obj.Signatures, signature)
|
obj.Signatures = append(obj.Signatures, signature)
|
||||||
@ -241,6 +283,12 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm)
|
||||||
|
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
|
||||||
|
alg, signatureAlgorithms)
|
||||||
|
}
|
||||||
|
|
||||||
if obj.Signatures[i].header != nil {
|
if obj.Signatures[i].header != nil {
|
||||||
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
|
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -260,7 +308,7 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
|||||||
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||||
jwk := obj.Signatures[i].Header.JSONWebKey
|
jwk := obj.Signatures[i].Header.JSONWebKey
|
||||||
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||||
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
return nil, errors.New("go-jose/go-jose: invalid embedded jwk, must be public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy value of sig
|
// Copy value of sig
|
||||||
@ -274,14 +322,18 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseSignedCompact parses a message in compact format.
|
// parseSignedCompact parses a message in compact format.
|
||||||
func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error) {
|
func parseSignedCompact(
|
||||||
|
input string,
|
||||||
|
payload []byte,
|
||||||
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
|
) (*JSONWebSignature, error) {
|
||||||
parts := strings.Split(input, ".")
|
parts := strings.Split(input, ".")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
|
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
|
||||||
}
|
}
|
||||||
|
|
||||||
if parts[1] != "" && payload != nil {
|
if parts[1] != "" && payload != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: payload is not detached")
|
return nil, fmt.Errorf("go-jose/go-jose: payload is not detached")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
@ -306,7 +358,7 @@ func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error)
|
|||||||
Protected: newBuffer(rawProtected),
|
Protected: newBuffer(rawProtected),
|
||||||
Signature: newBuffer(signature),
|
Signature: newBuffer(signature),
|
||||||
}
|
}
|
||||||
return raw.sanitized()
|
return raw.sanitized(signatureAlgorithms)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
|
func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
|
||||||
@ -314,15 +366,18 @@ func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
|
|||||||
return "", ErrNotSupported
|
return "", ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedProtected := base64.RawURLEncoding.EncodeToString(mustSerializeJSON(obj.Signatures[0].protected))
|
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
|
||||||
payload := ""
|
|
||||||
signature := base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)
|
|
||||||
|
|
||||||
|
var payload []byte
|
||||||
if !detached {
|
if !detached {
|
||||||
payload = base64.RawURLEncoding.EncodeToString(obj.payload)
|
payload = obj.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s.%s.%s", serializedProtected, payload, signature), nil
|
return base64JoinWithDots(
|
||||||
|
serializedProtected,
|
||||||
|
payload,
|
||||||
|
obj.Signatures[0].Signature,
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompactSerialize serializes an object using the compact serialization format.
|
// CompactSerialize serializes an object using the compact serialization format.
|
@ -83,6 +83,9 @@ func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg Sig
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpaqueKeyEncrypter is an interface that supports encrypting keys with an opaque key.
|
// OpaqueKeyEncrypter is an interface that supports encrypting keys with an opaque key.
|
||||||
|
//
|
||||||
|
// Note: this cannot currently be implemented outside this package because of its
|
||||||
|
// unexported method.
|
||||||
type OpaqueKeyEncrypter interface {
|
type OpaqueKeyEncrypter interface {
|
||||||
// KeyID returns the kid
|
// KeyID returns the kid
|
||||||
KeyID() string
|
KeyID() string
|
||||||
@ -121,7 +124,7 @@ func (oke *opaqueKeyEncrypter) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
|
|||||||
return oke.encrypter.encryptKey(cek, alg)
|
return oke.encrypter.encryptKey(cek, alg)
|
||||||
}
|
}
|
||||||
|
|
||||||
//OpaqueKeyDecrypter is an interface that supports decrypting keys with an opaque key.
|
// OpaqueKeyDecrypter is an interface that supports decrypting keys with an opaque key.
|
||||||
type OpaqueKeyDecrypter interface {
|
type OpaqueKeyDecrypter interface {
|
||||||
DecryptKey(encryptedKey []byte, header Header) ([]byte, error)
|
DecryptKey(encryptedKey []byte, header Header) ([]byte, error)
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyAlgorithm represents a key management algorithm.
|
// KeyAlgorithm represents a key management algorithm.
|
||||||
@ -45,32 +45,38 @@ var (
|
|||||||
// ErrCryptoFailure represents an error in cryptographic primitive. This
|
// ErrCryptoFailure represents an error in cryptographic primitive. This
|
||||||
// occurs when, for example, a message had an invalid authentication tag or
|
// occurs when, for example, a message had an invalid authentication tag or
|
||||||
// could not be decrypted.
|
// could not be decrypted.
|
||||||
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
|
ErrCryptoFailure = errors.New("go-jose/go-jose: error in cryptographic primitive")
|
||||||
|
|
||||||
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
|
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
|
||||||
// supported. This occurs when trying to instantiate an encrypter for an
|
// supported. This occurs when trying to instantiate an encrypter for an
|
||||||
// algorithm that is not yet implemented.
|
// algorithm that is not yet implemented.
|
||||||
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
|
ErrUnsupportedAlgorithm = errors.New("go-jose/go-jose: unknown/unsupported algorithm")
|
||||||
|
|
||||||
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
||||||
// supported. This occurs when trying to instantiate an encrypter and passing
|
// supported. This occurs when trying to instantiate an encrypter and passing
|
||||||
// it a key of an unrecognized type or with unsupported parameters, such as
|
// it a key of an unrecognized type or with unsupported parameters, such as
|
||||||
// an RSA private key with more than two primes.
|
// an RSA private key with more than two primes.
|
||||||
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
|
ErrUnsupportedKeyType = errors.New("go-jose/go-jose: unsupported key type/format")
|
||||||
|
|
||||||
// ErrInvalidKeySize indicates that the given key is not the correct size
|
// ErrInvalidKeySize indicates that the given key is not the correct size
|
||||||
// for the selected algorithm. This can occur, for example, when trying to
|
// for the selected algorithm. This can occur, for example, when trying to
|
||||||
// encrypt with AES-256 but passing only a 128-bit key as input.
|
// encrypt with AES-256 but passing only a 128-bit key as input.
|
||||||
ErrInvalidKeySize = errors.New("square/go-jose: invalid key size for algorithm")
|
ErrInvalidKeySize = errors.New("go-jose/go-jose: invalid key size for algorithm")
|
||||||
|
|
||||||
// ErrNotSupported serialization of object is not supported. This occurs when
|
// ErrNotSupported serialization of object is not supported. This occurs when
|
||||||
// trying to compact-serialize an object which can't be represented in
|
// trying to compact-serialize an object which can't be represented in
|
||||||
// compact form.
|
// compact form.
|
||||||
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
|
ErrNotSupported = errors.New("go-jose/go-jose: compact serialization not supported for object")
|
||||||
|
|
||||||
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
||||||
// nonce header parameter was included in an unprotected header object.
|
// nonce header parameter was included in an unprotected header object.
|
||||||
ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
|
ErrUnprotectedNonce = errors.New("go-jose/go-jose: Nonce parameter included in unprotected header")
|
||||||
|
|
||||||
|
// ErrMissingX5cHeader indicates that the JWT header is missing x5c headers.
|
||||||
|
ErrMissingX5cHeader = errors.New("go-jose/go-jose: no x5c header present in message")
|
||||||
|
|
||||||
|
// ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found.
|
||||||
|
ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Key management algorithms
|
// Key management algorithms
|
||||||
@ -133,8 +139,8 @@ const (
|
|||||||
type HeaderKey string
|
type HeaderKey string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HeaderType HeaderKey = "typ" // string
|
HeaderType = "typ" // string
|
||||||
HeaderContentType = "cty" // string
|
HeaderContentType = "cty" // string
|
||||||
|
|
||||||
// These are set by go-jose and shouldn't need to be set by consumers of the
|
// These are set by go-jose and shouldn't need to be set by consumers of the
|
||||||
// library.
|
// library.
|
||||||
@ -183,8 +189,13 @@ type Header struct {
|
|||||||
// Unverified certificate chain parsed from x5c header.
|
// Unverified certificate chain parsed from x5c header.
|
||||||
certificates []*x509.Certificate
|
certificates []*x509.Certificate
|
||||||
|
|
||||||
// Any headers not recognised above get unmarshalled
|
// At parse time, each header parameter with a name other than "kid",
|
||||||
// from JSON in a generic manner and placed in this map.
|
// "jwk", "alg", "nonce", or "x5c" will have its value passed to
|
||||||
|
// [json.Unmarshal] to unmarshal it into an interface value.
|
||||||
|
// The resulting value will be stored in this map, with the header
|
||||||
|
// parameter name as the key.
|
||||||
|
//
|
||||||
|
// [json.Unmarshal]: https://pkg.go.dev/encoding/json#Unmarshal
|
||||||
ExtraHeaders map[HeaderKey]interface{}
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +205,7 @@ type Header struct {
|
|||||||
// not be validated with the given verify options.
|
// not be validated with the given verify options.
|
||||||
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
||||||
if len(h.certificates) == 0 {
|
if len(h.certificates) == 0 {
|
||||||
return nil, errors.New("square/go-jose: no x5c header present in message")
|
return nil, ErrMissingX5cHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf := h.certificates[0]
|
leaf := h.certificates[0]
|
||||||
@ -452,8 +463,8 @@ func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dst rawHeader) isSet(k HeaderKey) bool {
|
func (parsed rawHeader) isSet(k HeaderKey) bool {
|
||||||
dvr := dst[k]
|
dvr := parsed[k]
|
||||||
if dvr == nil {
|
if dvr == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -472,17 +483,17 @@ func (dst rawHeader) isSet(k HeaderKey) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge headers from src into dst, giving precedence to headers from l.
|
// Merge headers from src into dst, giving precedence to headers from l.
|
||||||
func (dst rawHeader) merge(src *rawHeader) {
|
func (parsed rawHeader) merge(src *rawHeader) {
|
||||||
if src == nil {
|
if src == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range *src {
|
for k, v := range *src {
|
||||||
if dst.isSet(k) {
|
if parsed.isSet(k) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dst[k] = v
|
parsed[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,7 +507,7 @@ func curveName(crv elliptic.Curve) (string, error) {
|
|||||||
case elliptic.P521():
|
case elliptic.P521():
|
||||||
return "P-521", nil
|
return "P-521", nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
|
return "", ErrUnsupportedEllipticCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -19,14 +19,13 @@ package jose
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NonceSource represents a source of random nonces to go into JWS objects
|
// NonceSource represents a source of random nonces to go into JWS objects
|
||||||
@ -41,6 +40,20 @@ type Signer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SigningKey represents an algorithm/key used to sign a message.
|
// SigningKey represents an algorithm/key used to sign a message.
|
||||||
|
//
|
||||||
|
// Key must have one of these types:
|
||||||
|
// - ed25519.PrivateKey
|
||||||
|
// - *ecdsa.PrivateKey
|
||||||
|
// - *rsa.PrivateKey
|
||||||
|
// - *JSONWebKey
|
||||||
|
// - JSONWebKey
|
||||||
|
// - []byte (an HMAC key)
|
||||||
|
// - Any type that satisfies the OpaqueSigner interface
|
||||||
|
//
|
||||||
|
// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
|
||||||
|
// - HS256: 32 bytes
|
||||||
|
// - HS384: 48 bytes
|
||||||
|
// - HS512: 64 bytes
|
||||||
type SigningKey struct {
|
type SigningKey struct {
|
||||||
Algorithm SignatureAlgorithm
|
Algorithm SignatureAlgorithm
|
||||||
Key interface{}
|
Key interface{}
|
||||||
@ -53,12 +66,22 @@ type SignerOptions struct {
|
|||||||
|
|
||||||
// Optional map of additional keys to be inserted into the protected header
|
// Optional map of additional keys to be inserted into the protected header
|
||||||
// of a JWS object. Some specifications which make use of JWS like to insert
|
// of a JWS object. Some specifications which make use of JWS like to insert
|
||||||
// additional values here. All values must be JSON-serializable.
|
// additional values here.
|
||||||
|
//
|
||||||
|
// Values will be serialized by [json.Marshal] and must be valid inputs to
|
||||||
|
// that function.
|
||||||
|
//
|
||||||
|
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
|
||||||
ExtraHeaders map[HeaderKey]interface{}
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||||
// if necessary. It returns itself and so can be used in a fluent style.
|
// if necessary, and returns the updated SignerOptions.
|
||||||
|
//
|
||||||
|
// The v argument will be serialized by [json.Marshal] and must be a valid
|
||||||
|
// input to that function.
|
||||||
|
//
|
||||||
|
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
|
||||||
func (so *SignerOptions) WithHeader(k HeaderKey, v interface{}) *SignerOptions {
|
func (so *SignerOptions) WithHeader(k HeaderKey, v interface{}) *SignerOptions {
|
||||||
if so.ExtraHeaders == nil {
|
if so.ExtraHeaders == nil {
|
||||||
so.ExtraHeaders = map[HeaderKey]interface{}{}
|
so.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
@ -174,11 +197,11 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
|
|||||||
return newVerifier(verificationKey.Key)
|
return newVerifier(verificationKey.Key)
|
||||||
case *JSONWebKey:
|
case *JSONWebKey:
|
||||||
return newVerifier(verificationKey.Key)
|
return newVerifier(verificationKey.Key)
|
||||||
|
case OpaqueVerifier:
|
||||||
|
return &opaqueVerifier{verifier: verificationKey}, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
if ov, ok := verificationKey.(OpaqueVerifier); ok {
|
|
||||||
return &opaqueVerifier{verifier: ov}, nil
|
|
||||||
}
|
|
||||||
return nil, ErrUnsupportedKeyType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
|
func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
|
||||||
@ -205,11 +228,11 @@ func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipient
|
|||||||
return newJWKSigner(alg, signingKey)
|
return newJWKSigner(alg, signingKey)
|
||||||
case *JSONWebKey:
|
case *JSONWebKey:
|
||||||
return newJWKSigner(alg, *signingKey)
|
return newJWKSigner(alg, *signingKey)
|
||||||
|
case OpaqueSigner:
|
||||||
|
return newOpaqueSigner(alg, signingKey)
|
||||||
|
default:
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
if signer, ok := signingKey.(OpaqueSigner); ok {
|
|
||||||
return newOpaqueSigner(alg, signer)
|
|
||||||
}
|
|
||||||
return recipientSigInfo{}, ErrUnsupportedKeyType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
|
func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
|
||||||
@ -227,7 +250,7 @@ func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigIn
|
|||||||
|
|
||||||
// This should be impossible, but let's check anyway.
|
// This should be impossible, but let's check anyway.
|
||||||
if !recipient.publicKey().IsPublic() {
|
if !recipient.publicKey().IsPublic() {
|
||||||
return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public")
|
return recipientSigInfo{}, errors.New("go-jose/go-jose: public key was unexpectedly not public")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return recipient, nil
|
return recipient, nil
|
||||||
@ -251,7 +274,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
|||||||
// result of the JOSE spec. We've decided that this library will only include one or
|
// result of the JOSE spec. We've decided that this library will only include one or
|
||||||
// the other to avoid this confusion.
|
// the other to avoid this confusion.
|
||||||
//
|
//
|
||||||
// See https://github.com/square/go-jose/issues/157 for more context.
|
// See https://github.com/go-jose/go-jose/issues/157 for more context.
|
||||||
if ctx.embedJWK {
|
if ctx.embedJWK {
|
||||||
protected[headerJWK] = recipient.publicKey()
|
protected[headerJWK] = recipient.publicKey()
|
||||||
} else {
|
} else {
|
||||||
@ -265,7 +288,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
|||||||
if ctx.nonceSource != nil {
|
if ctx.nonceSource != nil {
|
||||||
nonce, err := ctx.nonceSource.Nonce()
|
nonce, err := ctx.nonceSource.Nonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
|
return nil, fmt.Errorf("go-jose/go-jose: Error generating nonce: %v", err)
|
||||||
}
|
}
|
||||||
protected[headerNonce] = nonce
|
protected[headerNonce] = nonce
|
||||||
}
|
}
|
||||||
@ -279,7 +302,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
|||||||
|
|
||||||
if b64, ok := protected[headerB64]; ok {
|
if b64, ok := protected[headerB64]; ok {
|
||||||
if needsBase64, ok = b64.(bool); !ok {
|
if needsBase64, ok = b64.(bool); !ok {
|
||||||
return nil, errors.New("square/go-jose: Invalid b64 header parameter")
|
return nil, errors.New("go-jose/go-jose: Invalid b64 header parameter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +326,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
|||||||
for k, v := range protected {
|
for k, v := range protected {
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: Error marshalling item %#v: %v", k, err)
|
return nil, fmt.Errorf("go-jose/go-jose: Error marshalling item %#v: %v", k, err)
|
||||||
}
|
}
|
||||||
(*signatureInfo.protected)[k] = makeRawMessage(b)
|
(*signatureInfo.protected)[k] = makeRawMessage(b)
|
||||||
}
|
}
|
||||||
@ -322,12 +345,28 @@ func (ctx *genericSigner) Options() SignerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify validates the signature on the object and returns the payload.
|
// Verify validates the signature on the object and returns the payload.
|
||||||
// This function does not support multi-signature, if you desire multi-sig
|
// This function does not support multi-signature. If you desire multi-signature
|
||||||
// verification use VerifyMulti instead.
|
// verification use VerifyMulti instead.
|
||||||
//
|
//
|
||||||
// Be careful when verifying signatures based on embedded JWKs inside the
|
// Be careful when verifying signatures based on embedded JWKs inside the
|
||||||
// payload header. You cannot assume that the key received in a payload is
|
// payload header. You cannot assume that the key received in a payload is
|
||||||
// trusted.
|
// trusted.
|
||||||
|
//
|
||||||
|
// The verificationKey argument must have one of these types:
|
||||||
|
// - ed25519.PublicKey
|
||||||
|
// - *ecdsa.PublicKey
|
||||||
|
// - *rsa.PublicKey
|
||||||
|
// - *JSONWebKey
|
||||||
|
// - JSONWebKey
|
||||||
|
// - *JSONWebKeySet
|
||||||
|
// - JSONWebKeySet
|
||||||
|
// - []byte (an HMAC key)
|
||||||
|
// - Any type that implements the OpaqueVerifier interface.
|
||||||
|
//
|
||||||
|
// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
|
||||||
|
// - HS256: 32 bytes
|
||||||
|
// - HS384: 48 bytes
|
||||||
|
// - HS512: 64 bytes
|
||||||
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
|
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
|
||||||
err := obj.DetachedVerify(obj.payload, verificationKey)
|
err := obj.DetachedVerify(obj.payload, verificationKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -347,14 +386,21 @@ func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
|
|||||||
// most cases, you will probably want to use Verify instead. DetachedVerify
|
// most cases, you will probably want to use Verify instead. DetachedVerify
|
||||||
// is only useful if you have a payload and signature that are separated from
|
// is only useful if you have a payload and signature that are separated from
|
||||||
// each other.
|
// each other.
|
||||||
|
//
|
||||||
|
// The verificationKey argument must have one of the types allowed for the
|
||||||
|
// verificationKey argument of JSONWebSignature.Verify().
|
||||||
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
|
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
|
||||||
verifier, err := newVerifier(verificationKey)
|
key, err := tryJWKS(verificationKey, obj.headers()...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifier, err := newVerifier(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(obj.Signatures) > 1 {
|
if len(obj.Signatures) > 1 {
|
||||||
return errors.New("square/go-jose: too many signatures in payload; expecting only one")
|
return errors.New("go-jose/go-jose: too many signatures in payload; expecting only one")
|
||||||
}
|
}
|
||||||
|
|
||||||
signature := obj.Signatures[0]
|
signature := obj.Signatures[0]
|
||||||
@ -388,6 +434,9 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter
|
|||||||
// returns the index of the signature that was verified, along with the signature
|
// returns the index of the signature that was verified, along with the signature
|
||||||
// object and the payload. We return the signature and index to guarantee that
|
// object and the payload. We return the signature and index to guarantee that
|
||||||
// callers are getting the verified value.
|
// callers are getting the verified value.
|
||||||
|
//
|
||||||
|
// The verificationKey argument must have one of the types allowed for the
|
||||||
|
// verificationKey argument of JSONWebSignature.Verify().
|
||||||
func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
|
func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
|
||||||
idx, sig, err := obj.DetachedVerifyMulti(obj.payload, verificationKey)
|
idx, sig, err := obj.DetachedVerifyMulti(obj.payload, verificationKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -405,8 +454,15 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa
|
|||||||
// DetachedVerifyMulti is only useful if you have a payload and signature that are
|
// DetachedVerifyMulti is only useful if you have a payload and signature that are
|
||||||
// separated from each other, and the signature can have multiple signers at the
|
// separated from each other, and the signature can have multiple signers at the
|
||||||
// same time.
|
// same time.
|
||||||
|
//
|
||||||
|
// The verificationKey argument must have one of the types allowed for the
|
||||||
|
// verificationKey argument of JSONWebSignature.Verify().
|
||||||
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
|
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
|
||||||
verifier, err := newVerifier(verificationKey)
|
key, err := tryJWKS(verificationKey, obj.headers()...)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Signature{}, err
|
||||||
|
}
|
||||||
|
verifier, err := newVerifier(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, Signature{}, err
|
return -1, Signature{}, err
|
||||||
}
|
}
|
||||||
@ -439,3 +495,11 @@ outer:
|
|||||||
|
|
||||||
return -1, Signature{}, ErrCryptoFailure
|
return -1, Signature{}, ErrCryptoFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj JSONWebSignature) headers() []Header {
|
||||||
|
headers := make([]Header, len(obj.Signatures))
|
||||||
|
for i, sig := range obj.Signatures {
|
||||||
|
headers[i] = sig.Header
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
@ -31,20 +31,26 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
"gopkg.in/square/go-jose.v2/cipher"
|
|
||||||
|
josecipher "github.com/go-jose/go-jose/v4/cipher"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Random reader (stubbed out in tests)
|
// RandReader is a cryptographically secure random number generator (stubbed out in tests).
|
||||||
var RandReader = rand.Reader
|
var RandReader = rand.Reader
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RFC7518 recommends a minimum of 1,000 iterations:
|
// RFC7518 recommends a minimum of 1,000 iterations:
|
||||||
// https://tools.ietf.org/html/rfc7518#section-4.8.1.2
|
// - https://tools.ietf.org/html/rfc7518#section-4.8.1.2
|
||||||
|
//
|
||||||
// NIST recommends a minimum of 10,000:
|
// NIST recommends a minimum of 10,000:
|
||||||
// https://pages.nist.gov/800-63-3/sp800-63b.html
|
// - https://pages.nist.gov/800-63-3/sp800-63b.html
|
||||||
// 1Password uses 100,000:
|
//
|
||||||
// https://support.1password.com/pbkdf2/
|
// 1Password increased in 2023 from 100,000 to 650,000:
|
||||||
defaultP2C = 100000
|
// - https://support.1password.com/pbkdf2/
|
||||||
|
//
|
||||||
|
// OWASP recommended 600,000 in Dec 2022:
|
||||||
|
// - https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
|
||||||
|
defaultP2C = 600000
|
||||||
// Default salt size: 128 bits
|
// Default salt size: 128 bits
|
||||||
defaultP2SSize = 16
|
defaultP2SSize = 16
|
||||||
)
|
)
|
||||||
@ -278,8 +284,14 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
|
|||||||
}
|
}
|
||||||
|
|
||||||
header := &rawHeader{}
|
header := &rawHeader{}
|
||||||
header.set(headerIV, newBuffer(parts.iv))
|
|
||||||
header.set(headerTag, newBuffer(parts.tag))
|
if err = header.set(headerIV, newBuffer(parts.iv)); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = header.set(headerTag, newBuffer(parts.tag)); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
return recipientInfo{
|
return recipientInfo{
|
||||||
header: header,
|
header: header,
|
||||||
@ -332,8 +344,14 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
|
|||||||
}
|
}
|
||||||
|
|
||||||
header := &rawHeader{}
|
header := &rawHeader{}
|
||||||
header.set(headerP2C, ctx.p2c)
|
|
||||||
header.set(headerP2S, newBuffer(ctx.p2s))
|
if err = header.set(headerP2C, ctx.p2c); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = header.set(headerP2S, newBuffer(ctx.p2s)); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
return recipientInfo{
|
return recipientInfo{
|
||||||
encryptedKey: jek,
|
encryptedKey: jek,
|
||||||
@ -356,11 +374,11 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
|
|||||||
|
|
||||||
iv, err := headers.getIV()
|
iv, err := headers.getIV()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid IV: %v", err)
|
return nil, fmt.Errorf("go-jose/go-jose: invalid IV: %v", err)
|
||||||
}
|
}
|
||||||
tag, err := headers.getTag()
|
tag, err := headers.getTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid tag: %v", err)
|
return nil, fmt.Errorf("go-jose/go-jose: invalid tag: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := &aeadParts{
|
parts := &aeadParts{
|
||||||
@ -389,18 +407,23 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
|
|||||||
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||||
p2s, err := headers.getP2S()
|
p2s, err := headers.getP2S()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid P2S: %v", err)
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: %v", err)
|
||||||
}
|
}
|
||||||
if p2s == nil || len(p2s.data) == 0 {
|
if p2s == nil || len(p2s.data) == 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid P2S: must be present")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: must be present")
|
||||||
}
|
}
|
||||||
|
|
||||||
p2c, err := headers.getP2C()
|
p2c, err := headers.getP2C()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid P2C: %v", err)
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: %v", err)
|
||||||
}
|
}
|
||||||
if p2c <= 0 {
|
if p2c <= 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid P2C: must be a positive integer")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: must be a positive integer")
|
||||||
|
}
|
||||||
|
if p2c > 1000000 {
|
||||||
|
// An unauthenticated attacker can set a high P2C value. Set an upper limit to avoid
|
||||||
|
// DoS attacks.
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: too high")
|
||||||
}
|
}
|
||||||
|
|
||||||
// salt is UTF8(Alg) || 0x00 || Salt Input
|
// salt is UTF8(Alg) || 0x00 || Salt Input
|
||||||
@ -431,7 +454,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
|
|||||||
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
mac, err := ctx.hmac(payload, alg)
|
mac, err := ctx.hmac(payload, alg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Signature{}, errors.New("square/go-jose: failed to compute hmac")
|
return Signature{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Signature{
|
return Signature{
|
||||||
@ -444,16 +467,16 @@ func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Sig
|
|||||||
func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error {
|
func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error {
|
||||||
expected, err := ctx.hmac(payload, alg)
|
expected, err := ctx.hmac(payload, alg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("square/go-jose: failed to compute hmac")
|
return errors.New("go-jose/go-jose: failed to compute hmac")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(mac) != len(expected) {
|
if len(mac) != len(expected) {
|
||||||
return errors.New("square/go-jose: invalid hmac")
|
return errors.New("go-jose/go-jose: invalid hmac")
|
||||||
}
|
}
|
||||||
|
|
||||||
match := subtle.ConstantTimeCompare(mac, expected)
|
match := subtle.ConstantTimeCompare(mac, expected)
|
||||||
if match != 1 {
|
if match != 1 {
|
||||||
return errors.New("square/go-jose: invalid hmac")
|
return errors.New("go-jose/go-jose: invalid hmac")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -463,12 +486,24 @@ func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureA
|
|||||||
func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
|
func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
|
||||||
var hash func() hash.Hash
|
var hash func() hash.Hash
|
||||||
|
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7518#section-3.2
|
||||||
|
// A key of the same size as the hash output (for instance, 256 bits for
|
||||||
|
// "HS256") or larger MUST be used
|
||||||
switch alg {
|
switch alg {
|
||||||
case HS256:
|
case HS256:
|
||||||
|
if len(ctx.key)*8 < 256 {
|
||||||
|
return nil, ErrInvalidKeySize
|
||||||
|
}
|
||||||
hash = sha256.New
|
hash = sha256.New
|
||||||
case HS384:
|
case HS384:
|
||||||
|
if len(ctx.key)*8 < 384 {
|
||||||
|
return nil, ErrInvalidKeySize
|
||||||
|
}
|
||||||
hash = sha512.New384
|
hash = sha512.New384
|
||||||
case HS512:
|
case HS512:
|
||||||
|
if len(ctx.key)*8 < 512 {
|
||||||
|
return nil, ErrInvalidKeySize
|
||||||
|
}
|
||||||
hash = sha512.New
|
hash = sha512.New
|
||||||
default:
|
default:
|
||||||
return nil, ErrUnsupportedAlgorithm
|
return nil, ErrUnsupportedAlgorithm
|
11
vendor/github.com/goccy/go-json/.golangci.yml
generated
vendored
11
vendor/github.com/goccy/go-json/.golangci.yml
generated
vendored
@ -48,6 +48,17 @@ linters:
|
|||||||
- nlreturn
|
- nlreturn
|
||||||
- testpackage
|
- testpackage
|
||||||
- wsl
|
- wsl
|
||||||
|
- varnamelen
|
||||||
|
- nilnil
|
||||||
|
- ireturn
|
||||||
|
- govet
|
||||||
|
- forcetypeassert
|
||||||
|
- cyclop
|
||||||
|
- containedctx
|
||||||
|
- revive
|
||||||
|
- nosnakecase
|
||||||
|
- exhaustruct
|
||||||
|
- depguard
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
165
vendor/github.com/goccy/go-json/CHANGELOG.md
generated
vendored
165
vendor/github.com/goccy/go-json/CHANGELOG.md
generated
vendored
@ -1,3 +1,168 @@
|
|||||||
|
# v0.10.2 - 2023/03/20
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
* Support DebugDOT option for debugging encoder ( #440 )
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix combination of embedding structure and omitempty option ( #442 )
|
||||||
|
|
||||||
|
# v0.10.1 - 2023/03/13
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix checkptr error for array decoder ( #415 )
|
||||||
|
* Fix added buffer size check when decoding key ( #430 )
|
||||||
|
* Fix handling of anonymous fields other than struct ( #431 )
|
||||||
|
* Fix to not optimize when lower conversion can't handle byte-by-byte ( #432 )
|
||||||
|
* Fix a problem that MarshalIndent does not work when UnorderedMap is specified ( #435 )
|
||||||
|
* Fix mapDecoder.DecodeStream() for empty objects containing whitespace ( #425 )
|
||||||
|
* Fix an issue that could not set the correct NextField for fields in the embedded structure ( #438 )
|
||||||
|
|
||||||
|
# v0.10.0 - 2022/11/29
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
* Support JSON Path ( #250 )
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix marshaler for map's key ( #409 )
|
||||||
|
|
||||||
|
# v0.9.11 - 2022/08/18
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix unexpected behavior when buffer ends with backslash ( #383 )
|
||||||
|
* Fix stream decoding of escaped character ( #387 )
|
||||||
|
|
||||||
|
# v0.9.10 - 2022/07/15
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix boundary exception of type caching ( #382 )
|
||||||
|
|
||||||
|
# v0.9.9 - 2022/07/15
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix encoding of directed interface with typed nil ( #377 )
|
||||||
|
* Fix embedded primitive type encoding using alias ( #378 )
|
||||||
|
* Fix slice/array type encoding with types implementing MarshalJSON ( #379 )
|
||||||
|
* Fix unicode decoding when the expected buffer state is not met after reading ( #380 )
|
||||||
|
|
||||||
|
# v0.9.8 - 2022/06/30
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix decoding of surrogate-pair ( #365 )
|
||||||
|
* Fix handling of embedded primitive type ( #366 )
|
||||||
|
* Add validation of escape sequence for decoder ( #367 )
|
||||||
|
* Fix stream tokenizing respecting UseNumber ( #369 )
|
||||||
|
* Fix encoding when struct pointer type that implements Marshal JSON is embedded ( #375 )
|
||||||
|
|
||||||
|
### Improve performance
|
||||||
|
|
||||||
|
* Improve performance of linkRecursiveCode ( #368 )
|
||||||
|
|
||||||
|
# v0.9.7 - 2022/04/22
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
#### Encoder
|
||||||
|
|
||||||
|
* Add filtering process for encoding on slow path ( #355 )
|
||||||
|
* Fix encoding of interface{} with pointer type ( #363 )
|
||||||
|
|
||||||
|
#### Decoder
|
||||||
|
|
||||||
|
* Fix map key decoder that implements UnmarshalJSON ( #353 )
|
||||||
|
* Fix decoding of []uint8 type ( #361 )
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
* Add DebugWith option for encoder ( #356 )
|
||||||
|
|
||||||
|
# v0.9.6 - 2022/03/22
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Correct the handling of the minimum value of int type for decoder ( #344 )
|
||||||
|
* Fix bugs of stream decoder's bufferSize ( #349 )
|
||||||
|
* Add a guard to use typeptr more safely ( #351 )
|
||||||
|
|
||||||
|
### Improve decoder performance
|
||||||
|
|
||||||
|
* Improve escapeString's performance ( #345 )
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* Update go version for CI ( #347 )
|
||||||
|
|
||||||
|
# v0.9.5 - 2022/03/04
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix panic when decoding time.Time with context ( #328 )
|
||||||
|
* Fix reading the next character in buffer to nul consideration ( #338 )
|
||||||
|
* Fix incorrect handling on skipValue ( #341 )
|
||||||
|
|
||||||
|
### Improve decoder performance
|
||||||
|
|
||||||
|
* Improve performance when a payload contains escape sequence ( #334 )
|
||||||
|
|
||||||
|
# v0.9.4 - 2022/01/21
|
||||||
|
|
||||||
|
* Fix IsNilForMarshaler for string type with omitempty ( #323 )
|
||||||
|
* Fix the case where the embedded field is at the end ( #326 )
|
||||||
|
|
||||||
|
# v0.9.3 - 2022/01/14
|
||||||
|
|
||||||
|
* Fix logic of removing struct field for decoder ( #322 )
|
||||||
|
|
||||||
|
# v0.9.2 - 2022/01/14
|
||||||
|
|
||||||
|
* Add invalid decoder to delay type error judgment at decode ( #321 )
|
||||||
|
|
||||||
|
# v0.9.1 - 2022/01/11
|
||||||
|
|
||||||
|
* Fix encoding of MarshalText/MarshalJSON operation with head offset ( #319 )
|
||||||
|
|
||||||
|
# v0.9.0 - 2022/01/05
|
||||||
|
|
||||||
|
### New feature
|
||||||
|
|
||||||
|
* Supports dynamic filtering of struct fields ( #314 )
|
||||||
|
|
||||||
|
### Improve encoding performance
|
||||||
|
|
||||||
|
* Improve map encoding performance ( #310 )
|
||||||
|
* Optimize encoding path for escaped string ( #311 )
|
||||||
|
* Add encoding option for performance ( #312 )
|
||||||
|
|
||||||
|
### Fix bugs
|
||||||
|
|
||||||
|
* Fix panic at encoding map value on 1.18 ( #310 )
|
||||||
|
* Fix MarshalIndent for interface type ( #317 )
|
||||||
|
|
||||||
|
# v0.8.1 - 2021/12/05
|
||||||
|
|
||||||
|
* Fix operation conversion from PtrHead to Head in Recursive type ( #305 )
|
||||||
|
|
||||||
|
# v0.8.0 - 2021/12/02
|
||||||
|
|
||||||
|
* Fix embedded field conflict behavior ( #300 )
|
||||||
|
* Refactor compiler for encoder ( #301 #302 )
|
||||||
|
|
||||||
|
# v0.7.10 - 2021/10/16
|
||||||
|
|
||||||
|
* Fix conversion from pointer to uint64 ( #294 )
|
||||||
|
|
||||||
|
# v0.7.9 - 2021/09/28
|
||||||
|
|
||||||
|
* Fix encoding of nil value about interface type that has method ( #291 )
|
||||||
|
|
||||||
# v0.7.8 - 2021/09/01
|
# v0.7.8 - 2021/09/01
|
||||||
|
|
||||||
* Fix mapassign_faststr for indirect struct type ( #283 )
|
* Fix mapassign_faststr for indirect struct type ( #283 )
|
||||||
|
4
vendor/github.com/goccy/go-json/Makefile
generated
vendored
4
vendor/github.com/goccy/go-json/Makefile
generated
vendored
@ -22,7 +22,7 @@ cover-html: cover
|
|||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: golangci-lint
|
lint: golangci-lint
|
||||||
golangci-lint run
|
$(BIN_DIR)/golangci-lint run
|
||||||
|
|
||||||
golangci-lint: | $(BIN_DIR)
|
golangci-lint: | $(BIN_DIR)
|
||||||
@{ \
|
@{ \
|
||||||
@ -30,7 +30,7 @@ golangci-lint: | $(BIN_DIR)
|
|||||||
GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
|
GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
|
||||||
cd $$GOLANGCI_LINT_TMP_DIR; \
|
cd $$GOLANGCI_LINT_TMP_DIR; \
|
||||||
go mod init tmp; \
|
go mod init tmp; \
|
||||||
GOBIN=$(BIN_DIR) go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.36.0; \
|
GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2; \
|
||||||
rm -rf $$GOLANGCI_LINT_TMP_DIR; \
|
rm -rf $$GOLANGCI_LINT_TMP_DIR; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
vendor/github.com/goccy/go-json/README.md
generated
vendored
8
vendor/github.com/goccy/go-json/README.md
generated
vendored
@ -13,7 +13,7 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
|
|||||||
```
|
```
|
||||||
* version ( expected release date )
|
* version ( expected release date )
|
||||||
|
|
||||||
* v0.7.0
|
* v0.9.0
|
||||||
|
|
|
|
||||||
| while maintaining compatibility with encoding/json, we will add convenient APIs
|
| while maintaining compatibility with encoding/json, we will add convenient APIs
|
||||||
|
|
|
|
||||||
@ -21,9 +21,8 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
|
|||||||
* v1.0.0
|
* v1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
We are accepting requests for features that will be implemented between v0.7.0 and v.1.0.0.
|
We are accepting requests for features that will be implemented between v0.9.0 and v.1.0.0.
|
||||||
If you have the API you need, please submit your issue [here](https://github.com/goccy/go-json/issues).
|
If you have the API you need, please submit your issue [here](https://github.com/goccy/go-json/issues).
|
||||||
For example, I'm thinking of supporting `context.Context` of `json.Marshaler` and decoding using JSON Path.
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
@ -32,6 +31,7 @@ For example, I'm thinking of supporting `context.Context` of `json.Marshaler` an
|
|||||||
- Flexible customization with options
|
- Flexible customization with options
|
||||||
- Coloring the encoded string
|
- Coloring the encoded string
|
||||||
- Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON`
|
- Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON`
|
||||||
|
- Can dynamically filter the fields of the structure type-safely
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ func Marshal(v interface{}) ([]byte, error) {
|
|||||||
`json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process.
|
`json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process.
|
||||||
In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped.
|
In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped.
|
||||||
|
|
||||||
Therefore, the arguments for `Marshal` and `Unmarshal` are always escape to the heap.
|
Therefore, the arguments for `Marshal` and `Unmarshal` are always escaped to the heap.
|
||||||
However, `go-json` can use the feature of `reflect.Type` while avoiding escaping.
|
However, `go-json` can use the feature of `reflect.Type` while avoiding escaping.
|
||||||
|
|
||||||
`reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package.
|
`reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package.
|
||||||
|
31
vendor/github.com/goccy/go-json/decode.go
generated
vendored
31
vendor/github.com/goccy/go-json/decode.go
generated
vendored
@ -83,6 +83,37 @@ func unmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs
|
|||||||
return validateEndBuf(src, cursor)
|
return validateEndBuf(src, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pathDecoder = decoder.NewPathDecoder()
|
||||||
|
)
|
||||||
|
|
||||||
|
func extractFromPath(path *Path, data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
|
||||||
|
if path.path.RootSelectorOnly {
|
||||||
|
return [][]byte{data}, nil
|
||||||
|
}
|
||||||
|
src := make([]byte, len(data)+1) // append nul byte to the end
|
||||||
|
copy(src, data)
|
||||||
|
|
||||||
|
ctx := decoder.TakeRuntimeContext()
|
||||||
|
ctx.Buf = src
|
||||||
|
ctx.Option.Flags = 0
|
||||||
|
ctx.Option.Flags |= decoder.PathOption
|
||||||
|
ctx.Option.Path = path.path
|
||||||
|
for _, optFunc := range optFuncs {
|
||||||
|
optFunc(ctx.Option)
|
||||||
|
}
|
||||||
|
paths, cursor, err := pathDecoder.DecodePath(ctx, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
decoder.ReleaseRuntimeContext(ctx)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decoder.ReleaseRuntimeContext(ctx)
|
||||||
|
if err := validateEndBuf(src, cursor); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
|
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
|
||||||
src := make([]byte, len(data)+1) // append nul byte to the end
|
src := make([]byte, len(data)+1) // append nul byte to the end
|
||||||
copy(src, data)
|
copy(src, data)
|
||||||
|
2
vendor/github.com/goccy/go-json/docker-compose.yml
generated
vendored
2
vendor/github.com/goccy/go-json/docker-compose.yml
generated
vendored
@ -1,7 +1,7 @@
|
|||||||
version: '2'
|
version: '2'
|
||||||
services:
|
services:
|
||||||
go-json:
|
go-json:
|
||||||
image: golang:1.16
|
image: golang:1.18
|
||||||
volumes:
|
volumes:
|
||||||
- '.:/go/src/go-json'
|
- '.:/go/src/go-json'
|
||||||
deploy:
|
deploy:
|
||||||
|
21
vendor/github.com/goccy/go-json/encode.go
generated
vendored
21
vendor/github.com/goccy/go-json/encode.go
generated
vendored
@ -3,6 +3,7 @@ package json
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/encoder"
|
"github.com/goccy/go-json/internal/encoder"
|
||||||
@ -51,7 +52,7 @@ func (e *Encoder) EncodeContext(ctx context.Context, v interface{}, optFuncs ...
|
|||||||
rctx.Option.Flag |= encoder.ContextOption
|
rctx.Option.Flag |= encoder.ContextOption
|
||||||
rctx.Option.Context = ctx
|
rctx.Option.Context = ctx
|
||||||
|
|
||||||
err := e.encodeWithOption(rctx, v, optFuncs...)
|
err := e.encodeWithOption(rctx, v, optFuncs...) //nolint: contextcheck
|
||||||
|
|
||||||
encoder.ReleaseRuntimeContext(rctx)
|
encoder.ReleaseRuntimeContext(rctx)
|
||||||
return err
|
return err
|
||||||
@ -61,6 +62,8 @@ func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v interface{}, o
|
|||||||
if e.enabledHTMLEscape {
|
if e.enabledHTMLEscape {
|
||||||
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
||||||
}
|
}
|
||||||
|
ctx.Option.Flag |= encoder.NormalizeUTF8Option
|
||||||
|
ctx.Option.DebugOut = os.Stdout
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(ctx.Option)
|
optFunc(ctx.Option)
|
||||||
}
|
}
|
||||||
@ -111,13 +114,13 @@ func (e *Encoder) SetIndent(prefix, indent string) {
|
|||||||
func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
|
func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
|
||||||
rctx := encoder.TakeRuntimeContext()
|
rctx := encoder.TakeRuntimeContext()
|
||||||
rctx.Option.Flag = 0
|
rctx.Option.Flag = 0
|
||||||
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.ContextOption
|
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.ContextOption
|
||||||
rctx.Option.Context = ctx
|
rctx.Option.Context = ctx
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(rctx.Option)
|
optFunc(rctx.Option)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := encode(rctx, v)
|
buf, err := encode(rctx, v) //nolint: contextcheck
|
||||||
if err != nil {
|
if err != nil {
|
||||||
encoder.ReleaseRuntimeContext(rctx)
|
encoder.ReleaseRuntimeContext(rctx)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -139,7 +142,7 @@ func marshal(v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
|
|||||||
ctx := encoder.TakeRuntimeContext()
|
ctx := encoder.TakeRuntimeContext()
|
||||||
|
|
||||||
ctx.Option.Flag = 0
|
ctx.Option.Flag = 0
|
||||||
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(ctx.Option)
|
optFunc(ctx.Option)
|
||||||
}
|
}
|
||||||
@ -166,7 +169,7 @@ func marshalNoEscape(v interface{}) ([]byte, error) {
|
|||||||
ctx := encoder.TakeRuntimeContext()
|
ctx := encoder.TakeRuntimeContext()
|
||||||
|
|
||||||
ctx.Option.Flag = 0
|
ctx.Option.Flag = 0
|
||||||
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
|
||||||
|
|
||||||
buf, err := encodeNoEscape(ctx, v)
|
buf, err := encodeNoEscape(ctx, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -190,7 +193,7 @@ func marshalIndent(v interface{}, prefix, indent string, optFuncs ...EncodeOptio
|
|||||||
ctx := encoder.TakeRuntimeContext()
|
ctx := encoder.TakeRuntimeContext()
|
||||||
|
|
||||||
ctx.Option.Flag = 0
|
ctx.Option.Flag = 0
|
||||||
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.IndentOption)
|
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.IndentOption)
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(ctx.Option)
|
optFunc(ctx.Option)
|
||||||
}
|
}
|
||||||
@ -220,7 +223,7 @@ func encode(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error) {
|
|||||||
typ := header.typ
|
typ := header.typ
|
||||||
|
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
|
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -248,7 +251,7 @@ func encodeNoEscape(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error)
|
|||||||
typ := header.typ
|
typ := header.typ
|
||||||
|
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
|
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -275,7 +278,7 @@ func encodeIndent(ctx *encoder.RuntimeContext, v interface{}, prefix, indent str
|
|||||||
typ := header.typ
|
typ := header.typ
|
||||||
|
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
|
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
2
vendor/github.com/goccy/go-json/error.go
generated
vendored
2
vendor/github.com/goccy/go-json/error.go
generated
vendored
@ -37,3 +37,5 @@ type UnmarshalTypeError = errors.UnmarshalTypeError
|
|||||||
type UnsupportedTypeError = errors.UnsupportedTypeError
|
type UnsupportedTypeError = errors.UnsupportedTypeError
|
||||||
|
|
||||||
type UnsupportedValueError = errors.UnsupportedValueError
|
type UnsupportedValueError = errors.UnsupportedValueError
|
||||||
|
|
||||||
|
type PathError = errors.PathError
|
||||||
|
4
vendor/github.com/goccy/go-json/internal/decoder/anonymous_field.go
generated
vendored
4
vendor/github.com/goccy/go-json/internal/decoder/anonymous_field.go
generated
vendored
@ -35,3 +35,7 @@ func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
|
|||||||
p = *(*unsafe.Pointer)(p)
|
p = *(*unsafe.Pointer)(p)
|
||||||
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
|
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *anonymousFieldDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return d.dec.DecodePath(ctx, cursor, depth)
|
||||||
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user