parent
ae8d4d8757
commit
bca311b05e
8 changed files with 104 additions and 15 deletions
|
@ -41,11 +41,12 @@ func (*fakeProvisioner) AuthorizeSign(ctx context.Context, token string) ([]prov
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (*fakeProvisioner) AuthorizeRevoke(ctx context.Context, token string) error { return nil }
|
||||
func (*fakeProvisioner) GetID() string { return "" }
|
||||
func (*fakeProvisioner) GetName() string { return "" }
|
||||
func (*fakeProvisioner) DefaultTLSCertDuration() time.Duration { return 0 }
|
||||
func (*fakeProvisioner) GetOptions() *provisioner.Options { return nil }
|
||||
func (*fakeProvisioner) AuthorizeRevoke(ctx context.Context, token string) error { return nil }
|
||||
func (*fakeProvisioner) AuthorizeChallenge(ctx context.Context, challenge string) error { return nil }
|
||||
func (*fakeProvisioner) GetID() string { return "" }
|
||||
func (*fakeProvisioner) GetName() string { return "" }
|
||||
func (*fakeProvisioner) DefaultTLSCertDuration() time.Duration { return 0 }
|
||||
func (*fakeProvisioner) GetOptions() *provisioner.Options { return nil }
|
||||
|
||||
func newProv() acme.Provisioner {
|
||||
// Initialize provisioners
|
||||
|
|
|
@ -255,8 +255,14 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
|
|||
}
|
||||
|
||||
db := acme.MustDatabaseFromContext(ctx)
|
||||
az.Challenges = make([]*acme.Challenge, len(chTypes))
|
||||
for i, typ := range chTypes {
|
||||
prov := acme.MustProvisionerFromContext(ctx)
|
||||
az.Challenges = make([]*acme.Challenge, 0, len(chTypes))
|
||||
for _, typ := range chTypes {
|
||||
// Make sure the challenge is enabled
|
||||
if err := prov.AuthorizeChallenge(ctx, string(typ)); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ch := &acme.Challenge{
|
||||
AccountID: az.AccountID,
|
||||
Value: az.Identifier.Value,
|
||||
|
@ -267,7 +273,7 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
|
|||
if err := db.CreateChallenge(ctx, ch); err != nil {
|
||||
return acme.WrapErrorISE(err, "error creating challenge")
|
||||
}
|
||||
az.Challenges[i] = ch
|
||||
az.Challenges = append(az.Challenges, ch)
|
||||
}
|
||||
if err = db.CreateAuthorization(ctx, az); err != nil {
|
||||
return acme.WrapErrorISE(err, "error creating authorization")
|
||||
|
|
|
@ -681,6 +681,7 @@ func TestHandler_newAuthorization(t *testing.T) {
|
|||
t.Run(name, func(t *testing.T) {
|
||||
tc := run(t)
|
||||
ctx := newBaseContext(context.Background(), tc.db)
|
||||
ctx = acme.NewProvisionerContext(ctx, newProv())
|
||||
if err := newAuthorization(ctx, tc.az); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
switch k := err.(type) {
|
||||
|
|
|
@ -71,6 +71,7 @@ type Provisioner interface {
|
|||
AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error
|
||||
AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error)
|
||||
AuthorizeRevoke(ctx context.Context, token string) error
|
||||
AuthorizeChallenge(ctx context.Context, challenge string) error
|
||||
GetID() string
|
||||
GetName() string
|
||||
DefaultTLSCertDuration() time.Duration
|
||||
|
@ -109,6 +110,7 @@ type MockProvisioner struct {
|
|||
MauthorizeOrderIdentifier func(ctx context.Context, identifier provisioner.ACMEIdentifier) error
|
||||
MauthorizeSign func(ctx context.Context, ott string) ([]provisioner.SignOption, error)
|
||||
MauthorizeRevoke func(ctx context.Context, token string) error
|
||||
MauthorizeChallenge func(Ctx context.Context, challenge string) error
|
||||
MdefaultTLSCertDuration func() time.Duration
|
||||
MgetOptions func() *provisioner.Options
|
||||
}
|
||||
|
@ -145,6 +147,14 @@ func (m *MockProvisioner) AuthorizeRevoke(ctx context.Context, token string) err
|
|||
return m.Merr
|
||||
}
|
||||
|
||||
// AuthorizeChallenge mock
|
||||
func (m *MockProvisioner) AuthorizeChallenge(ctx context.Context, challenge string) error {
|
||||
if m.MauthorizeChallenge != nil {
|
||||
return m.MauthorizeChallenge(ctx, challenge)
|
||||
}
|
||||
return m.Merr
|
||||
}
|
||||
|
||||
// DefaultTLSCertDuration mock
|
||||
func (m *MockProvisioner) DefaultTLSCertDuration() time.Duration {
|
||||
if m.MdefaultTLSCertDuration != nil {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -22,7 +23,11 @@ type ACME struct {
|
|||
// by clients when creating a new Account. If set to true, the provided
|
||||
// EAB will be verified. If set to false and an EAB is provided, it is
|
||||
// not verified. Defaults to false.
|
||||
RequireEAB bool `json:"requireEAB,omitempty"`
|
||||
RequireEAB bool `json:"requireEAB,omitempty"`
|
||||
// Challenges contains the enabled challenges for this provisioner. If this
|
||||
// value is not set the default http-01, dns-01 and tls-alpn-01 challenges
|
||||
// will be enabled, device-attest-01 will be disabled.
|
||||
Challenges []string `json:"challenges,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
Options *Options `json:"options,omitempty"`
|
||||
|
||||
|
@ -163,3 +168,21 @@ func (p *ACME) AuthorizeRevoke(ctx context.Context, token string) error {
|
|||
func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
return p.ctl.AuthorizeRenew(ctx, cert)
|
||||
}
|
||||
|
||||
// AuthorizeChallenge checks if the given challenge is enabled. By default
|
||||
// http-01, dns-01 and tls-alpn-01 are enabled, to disable any of them the
|
||||
// Challenge provisioner property should have at least one element.
|
||||
func (p *ACME) AuthorizeChallenge(ctx context.Context, challenge string) error {
|
||||
enabledChallenges := []string{
|
||||
"http-01", "dns-01", "tls-alpn-01",
|
||||
}
|
||||
if len(p.Challenges) > 0 {
|
||||
enabledChallenges = p.Challenges
|
||||
}
|
||||
for _, ch := range enabledChallenges {
|
||||
if strings.EqualFold(ch, challenge) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("acme challenge %q is disabled", challenge)
|
||||
}
|
||||
|
|
|
@ -753,6 +753,7 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
|
|||
Name: p.Name,
|
||||
ForceCN: cfg.ForceCn,
|
||||
RequireEAB: cfg.RequireEab,
|
||||
Challenges: challengesToCertificates(cfg.Challenges),
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
|
@ -1001,7 +1002,8 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro
|
|||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_ACME{
|
||||
ACME: &linkedca.ACMEProvisioner{
|
||||
ForceCn: p.ForceCN,
|
||||
ForceCn: p.ForceCN,
|
||||
Challenges: challengesToLinkedca(p.Challenges),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1122,3 +1124,41 @@ func parseInstanceAge(age string) (provisioner.Duration, error) {
|
|||
}
|
||||
return instanceAge, nil
|
||||
}
|
||||
|
||||
func challengesToCertificates(challenges []linkedca.ACMEProvisioner_ChallengeType) []string {
|
||||
ret := make([]string, len(challenges))
|
||||
for i, ch := range challenges {
|
||||
switch ch {
|
||||
case linkedca.ACMEProvisioner_HTTP_01:
|
||||
ret[i] = "http-01"
|
||||
case linkedca.ACMEProvisioner_DNS_01:
|
||||
ret[i] = "dns-01"
|
||||
case linkedca.ACMEProvisioner_TLS_ALPN_O1:
|
||||
ret[i] = "tls-alpn-01"
|
||||
case linkedca.ACMEProvisioner_DEVICE_ATTEST_01:
|
||||
ret[i] = "device-attest-01"
|
||||
default:
|
||||
ret[i] = "unknown"
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func challengesToLinkedca(challenges []string) []linkedca.ACMEProvisioner_ChallengeType {
|
||||
ret := make([]linkedca.ACMEProvisioner_ChallengeType, len(challenges))
|
||||
for i, ch := range challenges {
|
||||
switch ch {
|
||||
case "http-01":
|
||||
ret[i] = linkedca.ACMEProvisioner_DNS_01
|
||||
case "dns-01":
|
||||
ret[i] = linkedca.ACMEProvisioner_DNS_01
|
||||
case "tls-alpn-01":
|
||||
ret[i] = linkedca.ACMEProvisioner_TLS_ALPN_O1
|
||||
case "device-attest-01":
|
||||
ret[i] = linkedca.ACMEProvisioner_DEVICE_ATTEST_01
|
||||
default:
|
||||
ret[i] = linkedca.ACMEProvisioner_UNKNOWN
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
4
go.mod
4
go.mod
|
@ -37,8 +37,8 @@ require (
|
|||
github.com/urfave/cli v1.22.4
|
||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||
go.step.sm/cli-utils v0.7.3
|
||||
go.step.sm/crypto v0.17.3
|
||||
go.step.sm/linkedca v0.18.0
|
||||
go.step.sm/crypto v0.17.4-0.20220823173825-938e5638a882
|
||||
go.step.sm/linkedca v0.18.1-0.20220824000236-47827c8eb300
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d
|
||||
google.golang.org/api v0.84.0
|
||||
|
|
14
go.sum
14
go.sum
|
@ -247,6 +247,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
|||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-piv/piv-go v1.9.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM=
|
||||
github.com/go-piv/piv-go v1.10.0 h1:P1Y1VjBI5DnXW0+YkKmTuh5opWnMIrKriUaIOblee9Q=
|
||||
github.com/go-piv/piv-go v1.10.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
|
@ -570,6 +571,7 @@ github.com/micromdm/scep/v2 v2.1.0 h1:2fS9Rla7qRR266hvUoEauBJ7J6FhgssEiq2OkSKXma
|
|||
github.com/micromdm/scep/v2 v2.1.0/go.mod h1:BkF7TkPPhmgJAMtHfP+sFTKXmgzNJgLQlvvGoOExBcc=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
|
@ -751,6 +753,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
|
@ -771,10 +774,11 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
|||
go.step.sm/cli-utils v0.7.3 h1:IA12IaiXVCI18yOFVQuvMpyvjL8wuwUn1yO+KhAVAr0=
|
||||
go.step.sm/cli-utils v0.7.3/go.mod h1:RJRwbBLqzs5nrepQLAV9FuT3fVpWz66tKzLIB7Izpfk=
|
||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/crypto v0.17.3 h1:YxrRVGmtY4PObl8YD9bU0/nx7F29GjnFR9Ef+Z+cILk=
|
||||
go.step.sm/crypto v0.17.3/go.mod h1:FXFiLBUsoE0OGz8JTjxhYU1rwKKNgVIb5izZTUMdc/8=
|
||||
go.step.sm/linkedca v0.18.0 h1:uxRBd2WDvJNZ2i0nJm/QmG4lkRxWoebYKJinchX7T7o=
|
||||
go.step.sm/crypto v0.17.4-0.20220823173825-938e5638a882 h1:nNG9b4hUh1vN6yxqzQ/Q4Ex/s4di1qoMESxrGC4pMsY=
|
||||
go.step.sm/crypto v0.17.4-0.20220823173825-938e5638a882/go.mod h1:FXFiLBUsoE0OGz8JTjxhYU1rwKKNgVIb5izZTUMdc/8=
|
||||
go.step.sm/linkedca v0.18.0/go.mod h1:qSuYlIIhvPmA2+DSSS03E2IXhbXWTLW61Xh9zDQJ3VM=
|
||||
go.step.sm/linkedca v0.18.1-0.20220824000236-47827c8eb300 h1:kDqCHUh4jqqqf+m5IXjFjlwsTXuIXpf5ciGKigqJH14=
|
||||
go.step.sm/linkedca v0.18.1-0.20220824000236-47827c8eb300/go.mod h1:qSuYlIIhvPmA2+DSSS03E2IXhbXWTLW61Xh9zDQJ3VM=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
|
@ -887,6 +891,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
|
@ -992,6 +997,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -1090,6 +1096,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -1306,6 +1313,7 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
Loading…
Reference in a new issue