diff --git a/Gopkg.lock b/Gopkg.lock index c8b9f4239..e8b4586ec 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -49,6 +49,7 @@ "autorest", "autorest/adal", "autorest/azure", + "autorest/azure/auth", "autorest/date", "autorest/to" ] @@ -363,6 +364,12 @@ revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" version = "v3.2.0" +[[projects]] + name = "github.com/dimchansky/utfbom" + packages = ["."] + revision = "5448fe645cb1964ba70ac8f9f2ffe975e61a536c" + version = "v1.0.0" + [[projects]] branch = "master" name = "github.com/dnsimple/dnsimple-go" @@ -533,19 +540,6 @@ revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98" version = "v1.1.0" -[[projects]] - name = "github.com/edeckers/auroradnsclient" - packages = [ - ".", - "records", - "requests", - "requests/errors", - "tokens", - "zones" - ] - revision = "1563e622aaca0a8bb895a448f31d4a430ab97586" - version = "v1.0.3" - [[projects]] branch = "master" name = "github.com/elazarl/go-bindata-assetfs" @@ -863,6 +857,12 @@ packages = ["."] revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" +[[projects]] + name = "github.com/ldez/go-auroradns" + packages = ["."] + revision = "b40dfcae7c417f8129579362695dc1f3cfe5928d" + version = "v2.0.0" + [[projects]] branch = "master" name = "github.com/libkermit/compose" @@ -1398,7 +1398,7 @@ "providers/dns/vegadns", "providers/dns/vultr" ] - revision = "160d6fe60303699067faad57dc0b1e147ac499ef" + revision = "1151b4e3befc51b7b215179c87791753721dc6d5" [[projects]] branch = "master" @@ -1410,6 +1410,8 @@ "ed25519/internal/edwards25519", "ocsp", "pbkdf2", + "pkcs12", + "pkcs12/internal/rc2", "scrypt", "ssh/terminal" ] diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md index cfa527b8a..6cb617744 100644 --- a/docs/configuration/acme.md +++ b/docs/configuration/acme.md @@ -252,50 +252,51 @@ Useful if internal networks block external DNS queries. Here is a list of supported `provider`s, that can automate the DNS verification, along with the required environment variables and their [wildcard & root domain support](/configuration/acme/#wildcard-domains) for each. Do not hesitate to complete it. -| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support | -|--------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------| -| [Alibaba Cloud](https://www.vultr.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | Not tested yet | -| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | Not tested yet | -| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` | Not tested yet | -| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | Not tested yet | -| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` - The `Global API Key` needs to be used, not the `Origin CA Key` | YES | -| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | Not tested yet | -| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | YES | -| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet | -| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet | -| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet | -| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | YES | -| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | No | -| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet | -| External Program | `exec` | `EXEC_PATH` | Not tested yet | -| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | YES | -| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | Not tested yet | -| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | Not tested yet | -| [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | YES | -| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet | -| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | Not tested yet | -| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` | YES | -| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | Not tested yet | -| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | Not tested yet | -| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet | -| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet | -| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | Not tested yet | -| manual | - | none, but you need to run Traefik interactively, turn on `acmeLogging` to see instructions and press Enter. | YES | -| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | YES | -| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet | -| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | Not tested yet | -| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | Not tested yet | -| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet | -| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | Not tested yet | -| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | YES | -| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | Not tested yet | -| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | Not tested yet | -| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet | -| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | YES | -| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | Not tested yet | -| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | Not tested yet | -| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet | -| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet | +| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support | +|--------------------------------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------| +| [ACME DNS](https://github.com/joohoi/acme-dns) | `acmedns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | Not tested yet | +| [Alibaba Cloud](https://www.vultr.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | Not tested yet | +| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | Not tested yet | +| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | Not tested yet | +| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | Not tested yet | +| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` - The `Global API Key` needs to be used, not the `Origin CA Key` | YES | +| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | Not tested yet | +| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | YES | +| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet | +| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet | +| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet | +| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | YES | +| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | No | +| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet | +| External Program | `exec` | `EXEC_PATH` | Not tested yet | +| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | YES | +| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | Not tested yet | +| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | Not tested yet | +| [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | YES | +| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet | +| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | Not tested yet | +| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` | YES | +| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | Not tested yet | +| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | Not tested yet | +| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet | +| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet | +| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | Not tested yet | +| manual | - | none, but you need to run Traefik interactively, turn on `acmeLogging` to see instructions and press Enter. | YES | +| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | YES | +| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet | +| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | Not tested yet | +| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | Not tested yet | +| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet | +| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | Not tested yet | +| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | YES | +| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | Not tested yet | +| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | Not tested yet | +| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet | +| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | YES | +| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | Not tested yet | +| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | Not tested yet | +| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet | +| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet | ### `domains` diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/auth/auth.go b/vendor/github.com/Azure/go-autorest/autorest/azure/auth/auth.go new file mode 100644 index 000000000..dd89d9c9d --- /dev/null +++ b/vendor/github.com/Azure/go-autorest/autorest/azure/auth/auth.go @@ -0,0 +1,408 @@ +package auth + +// Copyright 2017 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + "unicode/utf16" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/adal" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/dimchansky/utfbom" + "golang.org/x/crypto/pkcs12" +) + +// NewAuthorizerFromEnvironment creates an Authorizer configured from environment variables in the order: +// 1. Client credentials +// 2. Client certificate +// 3. Username password +// 4. MSI +func NewAuthorizerFromEnvironment() (autorest.Authorizer, error) { + tenantID := os.Getenv("AZURE_TENANT_ID") + clientID := os.Getenv("AZURE_CLIENT_ID") + clientSecret := os.Getenv("AZURE_CLIENT_SECRET") + certificatePath := os.Getenv("AZURE_CERTIFICATE_PATH") + certificatePassword := os.Getenv("AZURE_CERTIFICATE_PASSWORD") + username := os.Getenv("AZURE_USERNAME") + password := os.Getenv("AZURE_PASSWORD") + envName := os.Getenv("AZURE_ENVIRONMENT") + resource := os.Getenv("AZURE_AD_RESOURCE") + + var env azure.Environment + if envName == "" { + env = azure.PublicCloud + } else { + var err error + env, err = azure.EnvironmentFromName(envName) + if err != nil { + return nil, err + } + } + + if resource == "" { + resource = env.ResourceManagerEndpoint + } + + //1.Client Credentials + if clientSecret != "" { + config := NewClientCredentialsConfig(clientID, clientSecret, tenantID) + config.AADEndpoint = env.ActiveDirectoryEndpoint + config.Resource = resource + return config.Authorizer() + } + + //2. Client Certificate + if certificatePath != "" { + config := NewClientCertificateConfig(certificatePath, certificatePassword, clientID, tenantID) + config.AADEndpoint = env.ActiveDirectoryEndpoint + config.Resource = resource + return config.Authorizer() + } + + //3. Username Password + if username != "" && password != "" { + config := NewUsernamePasswordConfig(username, password, clientID, tenantID) + config.AADEndpoint = env.ActiveDirectoryEndpoint + config.Resource = resource + return config.Authorizer() + } + + // 4. MSI + config := NewMSIConfig() + config.Resource = resource + config.ClientID = clientID + return config.Authorizer() +} + +// NewAuthorizerFromFile creates an Authorizer configured from a configuration file. +func NewAuthorizerFromFile(baseURI string) (autorest.Authorizer, error) { + fileLocation := os.Getenv("AZURE_AUTH_LOCATION") + if fileLocation == "" { + return nil, errors.New("auth file not found. Environment variable AZURE_AUTH_LOCATION is not set") + } + + contents, err := ioutil.ReadFile(fileLocation) + if err != nil { + return nil, err + } + + // Auth file might be encoded + decoded, err := decode(contents) + if err != nil { + return nil, err + } + + file := file{} + err = json.Unmarshal(decoded, &file) + if err != nil { + return nil, err + } + + resource, err := getResourceForToken(file, baseURI) + if err != nil { + return nil, err + } + + config, err := adal.NewOAuthConfig(file.ActiveDirectoryEndpoint, file.TenantID) + if err != nil { + return nil, err + } + + spToken, err := adal.NewServicePrincipalToken(*config, file.ClientID, file.ClientSecret, resource) + if err != nil { + return nil, err + } + + return autorest.NewBearerAuthorizer(spToken), nil +} + +// File represents the authentication file +type file struct { + ClientID string `json:"clientId,omitempty"` + ClientSecret string `json:"clientSecret,omitempty"` + SubscriptionID string `json:"subscriptionId,omitempty"` + TenantID string `json:"tenantId,omitempty"` + ActiveDirectoryEndpoint string `json:"activeDirectoryEndpointUrl,omitempty"` + ResourceManagerEndpoint string `json:"resourceManagerEndpointUrl,omitempty"` + GraphResourceID string `json:"activeDirectoryGraphResourceId,omitempty"` + SQLManagementEndpoint string `json:"sqlManagementEndpointUrl,omitempty"` + GalleryEndpoint string `json:"galleryEndpointUrl,omitempty"` + ManagementEndpoint string `json:"managementEndpointUrl,omitempty"` +} + +func decode(b []byte) ([]byte, error) { + reader, enc := utfbom.Skip(bytes.NewReader(b)) + + switch enc { + case utfbom.UTF16LittleEndian: + u16 := make([]uint16, (len(b)/2)-1) + err := binary.Read(reader, binary.LittleEndian, &u16) + if err != nil { + return nil, err + } + return []byte(string(utf16.Decode(u16))), nil + case utfbom.UTF16BigEndian: + u16 := make([]uint16, (len(b)/2)-1) + err := binary.Read(reader, binary.BigEndian, &u16) + if err != nil { + return nil, err + } + return []byte(string(utf16.Decode(u16))), nil + } + return ioutil.ReadAll(reader) +} + +func getResourceForToken(f file, baseURI string) (string, error) { + // Compare dafault base URI from the SDK to the endpoints from the public cloud + // Base URI and token resource are the same string. This func finds the authentication + // file field that matches the SDK base URI. The SDK defines the public cloud + // endpoint as its default base URI + if !strings.HasSuffix(baseURI, "/") { + baseURI += "/" + } + switch baseURI { + case azure.PublicCloud.ServiceManagementEndpoint: + return f.ManagementEndpoint, nil + case azure.PublicCloud.ResourceManagerEndpoint: + return f.ResourceManagerEndpoint, nil + case azure.PublicCloud.ActiveDirectoryEndpoint: + return f.ActiveDirectoryEndpoint, nil + case azure.PublicCloud.GalleryEndpoint: + return f.GalleryEndpoint, nil + case azure.PublicCloud.GraphEndpoint: + return f.GraphResourceID, nil + } + return "", fmt.Errorf("auth: base URI not found in endpoints") +} + +// NewClientCredentialsConfig creates an AuthorizerConfig object configured to obtain an Authorizer through Client Credentials. +// Defaults to Public Cloud and Resource Manager Endpoint. +func NewClientCredentialsConfig(clientID string, clientSecret string, tenantID string) ClientCredentialsConfig { + return ClientCredentialsConfig{ + ClientID: clientID, + ClientSecret: clientSecret, + TenantID: tenantID, + Resource: azure.PublicCloud.ResourceManagerEndpoint, + AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, + } +} + +// NewClientCertificateConfig creates a ClientCertificateConfig object configured to obtain an Authorizer through client certificate. +// Defaults to Public Cloud and Resource Manager Endpoint. +func NewClientCertificateConfig(certificatePath string, certificatePassword string, clientID string, tenantID string) ClientCertificateConfig { + return ClientCertificateConfig{ + CertificatePath: certificatePath, + CertificatePassword: certificatePassword, + ClientID: clientID, + TenantID: tenantID, + Resource: azure.PublicCloud.ResourceManagerEndpoint, + AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, + } +} + +// NewUsernamePasswordConfig creates an UsernamePasswordConfig object configured to obtain an Authorizer through username and password. +// Defaults to Public Cloud and Resource Manager Endpoint. +func NewUsernamePasswordConfig(username string, password string, clientID string, tenantID string) UsernamePasswordConfig { + return UsernamePasswordConfig{ + Username: username, + Password: password, + ClientID: clientID, + TenantID: tenantID, + Resource: azure.PublicCloud.ResourceManagerEndpoint, + AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, + } +} + +// NewMSIConfig creates an MSIConfig object configured to obtain an Authorizer through MSI. +func NewMSIConfig() MSIConfig { + return MSIConfig{ + Resource: azure.PublicCloud.ResourceManagerEndpoint, + } +} + +// NewDeviceFlowConfig creates a DeviceFlowConfig object configured to obtain an Authorizer through device flow. +// Defaults to Public Cloud and Resource Manager Endpoint. +func NewDeviceFlowConfig(clientID string, tenantID string) DeviceFlowConfig { + return DeviceFlowConfig{ + ClientID: clientID, + TenantID: tenantID, + Resource: azure.PublicCloud.ResourceManagerEndpoint, + AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, + } +} + +//AuthorizerConfig provides an authorizer from the configuration provided. +type AuthorizerConfig interface { + Authorizer() (autorest.Authorizer, error) +} + +// ClientCredentialsConfig provides the options to get a bearer authorizer from client credentials. +type ClientCredentialsConfig struct { + ClientID string + ClientSecret string + TenantID string + AADEndpoint string + Resource string +} + +// Authorizer gets the authorizer from client credentials. +func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) { + oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID) + if err != nil { + return nil, err + } + + spToken, err := adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource) + if err != nil { + return nil, fmt.Errorf("failed to get oauth token from client credentials: %v", err) + } + + return autorest.NewBearerAuthorizer(spToken), nil +} + +// ClientCertificateConfig provides the options to get a bearer authorizer from a client certificate. +type ClientCertificateConfig struct { + ClientID string + CertificatePath string + CertificatePassword string + TenantID string + AADEndpoint string + Resource string +} + +// Authorizer gets an authorizer object from client certificate. +func (ccc ClientCertificateConfig) Authorizer() (autorest.Authorizer, error) { + oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID) + + certData, err := ioutil.ReadFile(ccc.CertificatePath) + if err != nil { + return nil, fmt.Errorf("failed to read the certificate file (%s): %v", ccc.CertificatePath, err) + } + + certificate, rsaPrivateKey, err := decodePkcs12(certData, ccc.CertificatePassword) + if err != nil { + return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err) + } + + spToken, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource) + + if err != nil { + return nil, fmt.Errorf("failed to get oauth token from certificate auth: %v", err) + } + + return autorest.NewBearerAuthorizer(spToken), nil +} + +// DeviceFlowConfig provides the options to get a bearer authorizer using device flow authentication. +type DeviceFlowConfig struct { + ClientID string + TenantID string + AADEndpoint string + Resource string +} + +// Authorizer gets the authorizer from device flow. +func (dfc DeviceFlowConfig) Authorizer() (autorest.Authorizer, error) { + oauthClient := &autorest.Client{} + oauthConfig, err := adal.NewOAuthConfig(dfc.AADEndpoint, dfc.TenantID) + deviceCode, err := adal.InitiateDeviceAuth(oauthClient, *oauthConfig, dfc.ClientID, dfc.AADEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to start device auth flow: %s", err) + } + + log.Println(*deviceCode.Message) + + token, err := adal.WaitForUserCompletion(oauthClient, deviceCode) + if err != nil { + return nil, fmt.Errorf("failed to finish device auth flow: %s", err) + } + + spToken, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, dfc.ClientID, dfc.Resource, *token) + if err != nil { + return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err) + } + + return autorest.NewBearerAuthorizer(spToken), nil +} + +func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { + privateKey, certificate, err := pkcs12.Decode(pkcs, password) + if err != nil { + return nil, nil, err + } + + rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) + if !isRsaKey { + return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key") + } + + return certificate, rsaPrivateKey, nil +} + +// UsernamePasswordConfig provides the options to get a bearer authorizer from a username and a password. +type UsernamePasswordConfig struct { + ClientID string + Username string + Password string + TenantID string + AADEndpoint string + Resource string +} + +// Authorizer gets the authorizer from a username and a password. +func (ups UsernamePasswordConfig) Authorizer() (autorest.Authorizer, error) { + + oauthConfig, err := adal.NewOAuthConfig(ups.AADEndpoint, ups.TenantID) + + spToken, err := adal.NewServicePrincipalTokenFromUsernamePassword(*oauthConfig, ups.ClientID, ups.Username, ups.Password, ups.Resource) + + if err != nil { + return nil, fmt.Errorf("failed to get oauth token from username and password auth: %v", err) + } + + return autorest.NewBearerAuthorizer(spToken), nil +} + +// MSIConfig provides the options to get a bearer authorizer through MSI. +type MSIConfig struct { + Resource string + ClientID string +} + +// Authorizer gets the authorizer from MSI. +func (mc MSIConfig) Authorizer() (autorest.Authorizer, error) { + msiEndpoint, err := adal.GetMSIVMEndpoint() + if err != nil { + return nil, err + } + + spToken, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, mc.Resource) + if err != nil { + return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err) + } + + return autorest.NewBearerAuthorizer(spToken), nil +} diff --git a/vendor/github.com/dimchansky/utfbom/LICENSE b/vendor/github.com/dimchansky/utfbom/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/vendor/github.com/dimchansky/utfbom/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/dimchansky/utfbom/utfbom.go b/vendor/github.com/dimchansky/utfbom/utfbom.go new file mode 100644 index 000000000..648184a12 --- /dev/null +++ b/vendor/github.com/dimchansky/utfbom/utfbom.go @@ -0,0 +1,174 @@ +// Package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary. +// It wraps an io.Reader object, creating another object (Reader) that also implements the io.Reader +// interface but provides automatic BOM checking and removing as necessary. +package utfbom + +import ( + "errors" + "io" +) + +// Encoding is type alias for detected UTF encoding. +type Encoding int + +// Constants to identify detected UTF encodings. +const ( + // Unknown encoding, returned when no BOM was detected + Unknown Encoding = iota + + // UTF8, BOM bytes: EF BB BF + UTF8 + + // UTF-16, big-endian, BOM bytes: FE FF + UTF16BigEndian + + // UTF-16, little-endian, BOM bytes: FF FE + UTF16LittleEndian + + // UTF-32, big-endian, BOM bytes: 00 00 FE FF + UTF32BigEndian + + // UTF-32, little-endian, BOM bytes: FF FE 00 00 + UTF32LittleEndian +) + +const maxConsecutiveEmptyReads = 100 + +// Skip creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary. +// It also returns the encoding detected by the BOM. +// If the detected encoding is not needed, you can call the SkipOnly function. +func Skip(rd io.Reader) (*Reader, Encoding) { + // Is it already a Reader? + b, ok := rd.(*Reader) + if ok { + return b, Unknown + } + + enc, left, err := detectUtf(rd) + return &Reader{ + rd: rd, + buf: left, + err: err, + }, enc +} + +// SkipOnly creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary. +func SkipOnly(rd io.Reader) *Reader { + r, _ := Skip(rd) + return r +} + +// Reader implements automatic BOM (Unicode Byte Order Mark) checking and +// removing as necessary for an io.Reader object. +type Reader struct { + rd io.Reader // reader provided by the client + buf []byte // buffered data + err error // last error +} + +// Read is an implementation of io.Reader interface. +// The bytes are taken from the underlying Reader, but it checks for BOMs, removing them as necessary. +func (r *Reader) Read(p []byte) (n int, err error) { + if len(p) == 0 { + return 0, nil + } + + if r.buf == nil { + if r.err != nil { + return 0, r.readErr() + } + + return r.rd.Read(p) + } + + // copy as much as we can + n = copy(p, r.buf) + r.buf = nilIfEmpty(r.buf[n:]) + return n, nil +} + +func (r *Reader) readErr() error { + err := r.err + r.err = nil + return err +} + +var errNegativeRead = errors.New("utfbom: reader returned negative count from Read") + +func detectUtf(rd io.Reader) (enc Encoding, buf []byte, err error) { + buf, err = readBOM(rd) + + if len(buf) >= 4 { + if isUTF32BigEndianBOM4(buf) { + return UTF32BigEndian, nilIfEmpty(buf[4:]), err + } + if isUTF32LittleEndianBOM4(buf) { + return UTF32LittleEndian, nilIfEmpty(buf[4:]), err + } + } + + if len(buf) > 2 && isUTF8BOM3(buf) { + return UTF8, nilIfEmpty(buf[3:]), err + } + + if (err != nil && err != io.EOF) || (len(buf) < 2) { + return Unknown, nilIfEmpty(buf), err + } + + if isUTF16BigEndianBOM2(buf) { + return UTF16BigEndian, nilIfEmpty(buf[2:]), err + } + if isUTF16LittleEndianBOM2(buf) { + return UTF16LittleEndian, nilIfEmpty(buf[2:]), err + } + + return Unknown, nilIfEmpty(buf), err +} + +func readBOM(rd io.Reader) (buf []byte, err error) { + const maxBOMSize = 4 + var bom [maxBOMSize]byte // used to read BOM + + // read as many bytes as possible + for nEmpty, n := 0, 0; err == nil && len(buf) < maxBOMSize; buf = bom[:len(buf)+n] { + if n, err = rd.Read(bom[len(buf):]); n < 0 { + panic(errNegativeRead) + } + if n > 0 { + nEmpty = 0 + } else { + nEmpty++ + if nEmpty >= maxConsecutiveEmptyReads { + err = io.ErrNoProgress + } + } + } + return +} + +func isUTF32BigEndianBOM4(buf []byte) bool { + return buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF +} + +func isUTF32LittleEndianBOM4(buf []byte) bool { + return buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00 +} + +func isUTF8BOM3(buf []byte) bool { + return buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF +} + +func isUTF16BigEndianBOM2(buf []byte) bool { + return buf[0] == 0xFE && buf[1] == 0xFF +} + +func isUTF16LittleEndianBOM2(buf []byte) bool { + return buf[0] == 0xFF && buf[1] == 0xFE +} + +func nilIfEmpty(buf []byte) (res []byte) { + if len(buf) > 0 { + res = buf + } + return +} diff --git a/vendor/github.com/edeckers/auroradnsclient/client.go b/vendor/github.com/edeckers/auroradnsclient/client.go deleted file mode 100644 index d366afef3..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/client.go +++ /dev/null @@ -1,22 +0,0 @@ -package auroradnsclient - -import ( - "github.com/edeckers/auroradnsclient/requests" -) - -// AuroraDNSClient is a client for accessing the Aurora DNS API -type AuroraDNSClient struct { - requestor *requests.AuroraRequestor -} - -// NewAuroraDNSClient instantiates a new client -func NewAuroraDNSClient(endpoint string, userID string, key string) (*AuroraDNSClient, error) { - requestor, err := requests.NewAuroraRequestor(endpoint, userID, key) - if err != nil { - return nil, err - } - - return &AuroraDNSClient{ - requestor: requestor, - }, nil -} diff --git a/vendor/github.com/edeckers/auroradnsclient/errors.go b/vendor/github.com/edeckers/auroradnsclient/errors.go deleted file mode 100644 index 452718aa4..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -package auroradnsclient - -// AuroraDNSError describes the format of a generic AuroraDNS API error -type AuroraDNSError struct { - ErrorCode string `json:"error"` - Message string `json:"errormsg"` -} - -func (e AuroraDNSError) Error() string { - return e.Message -} diff --git a/vendor/github.com/edeckers/auroradnsclient/records.go b/vendor/github.com/edeckers/auroradnsclient/records.go deleted file mode 100644 index e40786e80..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/records.go +++ /dev/null @@ -1,75 +0,0 @@ -package auroradnsclient - -import ( - "encoding/json" - "fmt" - - "github.com/edeckers/auroradnsclient/records" - "github.com/sirupsen/logrus" -) - -// GetRecords returns a list of all records in given zone -func (client *AuroraDNSClient) GetRecords(zoneID string) ([]records.GetRecordsResponse, error) { - logrus.Debugf("GetRecords(%s)", zoneID) - relativeURL := fmt.Sprintf("zones/%s/records", zoneID) - - response, err := client.requestor.Request(relativeURL, "GET", []byte("")) - if err != nil { - logrus.Errorf("Failed to receive records: %s", err) - return nil, err - } - - var respData []records.GetRecordsResponse - err = json.Unmarshal(response, &respData) - if err != nil { - logrus.Errorf("Failed to unmarshall response: %s", err) - return nil, err - } - - return respData, nil -} - -// CreateRecord creates a new record in given zone -func (client *AuroraDNSClient) CreateRecord(zoneID string, data records.CreateRecordRequest) (*records.CreateRecordResponse, error) { - logrus.Debugf("CreateRecord(%s, %+v)", zoneID, data) - body, err := json.Marshal(data) - if err != nil { - logrus.Errorf("Failed to marshall request body: %s", err) - - return nil, err - } - - relativeURL := fmt.Sprintf("zones/%s/records", zoneID) - - response, err := client.requestor.Request(relativeURL, "POST", body) - if err != nil { - logrus.Errorf("Failed to create record: %s", err) - - return nil, err - } - - var respData *records.CreateRecordResponse - err = json.Unmarshal(response, &respData) - if err != nil { - logrus.Errorf("Failed to unmarshall response: %s", err) - - return nil, err - } - - return respData, nil -} - -// RemoveRecord removes a record corresponding to a particular id in a given zone -func (client *AuroraDNSClient) RemoveRecord(zoneID string, recordID string) (*records.RemoveRecordResponse, error) { - logrus.Debugf("RemoveRecord(%s, %s)", zoneID, recordID) - relativeURL := fmt.Sprintf("zones/%s/records/%s", zoneID, recordID) - - _, err := client.requestor.Request(relativeURL, "DELETE", nil) - if err != nil { - logrus.Errorf("Failed to remove record: %s", err) - - return nil, err - } - - return &records.RemoveRecordResponse{}, nil -} diff --git a/vendor/github.com/edeckers/auroradnsclient/records/datatypes.go b/vendor/github.com/edeckers/auroradnsclient/records/datatypes.go deleted file mode 100644 index ce7efa7ec..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/records/datatypes.go +++ /dev/null @@ -1,31 +0,0 @@ -package records - -// CreateRecordRequest describes the json payload for creating a record -type CreateRecordRequest struct { - RecordType string `json:"type"` - Name string `json:"name"` - Content string `json:"content"` - TTL int `json:"ttl"` -} - -// CreateRecordResponse describes the json response for creating a record -type CreateRecordResponse struct { - ID string `json:"id"` - RecordType string `json:"type"` - Name string `json:"name"` - Content string `json:"content"` - TTL int `json:"ttl"` -} - -// GetRecordsResponse describes the json response of a single record -type GetRecordsResponse struct { - ID string `json:"id"` - RecordType string `json:"type"` - Name string `json:"name"` - Content string `json:"content"` - TTL int `json:"ttl"` -} - -// RemoveRecordResponse describes the json response for removing a record -type RemoveRecordResponse struct { -} diff --git a/vendor/github.com/edeckers/auroradnsclient/requests/errors/errors.go b/vendor/github.com/edeckers/auroradnsclient/requests/errors/errors.go deleted file mode 100644 index e6d7a5649..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/requests/errors/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -package errors - -// BadRequest HTTP error wrapper -type BadRequest error - -// Unauthorized HTTP error wrapper -type Unauthorized error - -// Forbidden HTTP error wrapper -type Forbidden error - -// NotFound HTTP error wrapper -type NotFound error - -// ServerError HTTP error wrapper -type ServerError error - -// InvalidStatusCodeError is used when none of the other types applies -type InvalidStatusCodeError error diff --git a/vendor/github.com/edeckers/auroradnsclient/requests/requestor.go b/vendor/github.com/edeckers/auroradnsclient/requests/requestor.go deleted file mode 100644 index b01829c9f..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/requests/requestor.go +++ /dev/null @@ -1,124 +0,0 @@ -package requests - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/http/httputil" - "time" - - request_errors "github.com/edeckers/auroradnsclient/requests/errors" - "github.com/edeckers/auroradnsclient/tokens" - "github.com/sirupsen/logrus" -) - -// AuroraRequestor performs actual requests to API -type AuroraRequestor struct { - endpoint string - userID string - key string -} - -// NewAuroraRequestor instantiates a new requestor -func NewAuroraRequestor(endpoint string, userID string, key string) (*AuroraRequestor, error) { - if endpoint == "" { - return nil, fmt.Errorf("Aurora endpoint missing") - } - - if userID == "" || key == "" { - return nil, fmt.Errorf("Aurora credentials missing") - } - - return &AuroraRequestor{endpoint: endpoint, userID: userID, key: key}, nil -} - -func (requestor *AuroraRequestor) buildRequest(relativeURL string, method string, body []byte) (*http.Request, error) { - url := fmt.Sprintf("%s/%s", requestor.endpoint, relativeURL) - - request, err := http.NewRequest(method, url, bytes.NewReader(body)) - if err != nil { - logrus.Errorf("Failed to build request: %s", err) - - return request, err - } - - timestamp := time.Now().UTC() - fmtTime := timestamp.Format("20060102T150405Z") - - token := tokens.NewToken(requestor.userID, requestor.key, method, fmt.Sprintf("/%s", relativeURL), timestamp) - - request.Header.Set("X-AuroraDNS-Date", fmtTime) - request.Header.Set("Authorization", fmt.Sprintf("AuroraDNSv1 %s", token)) - - request.Header.Set("Content-Type", "application/json") - - rawRequest, err := httputil.DumpRequestOut(request, true) - if err != nil { - logrus.Errorf("Failed to dump request: %s", err) - } - - logrus.Debugf("Built request:\n%s", rawRequest) - - return request, err -} - -func (requestor *AuroraRequestor) testInvalidResponse(resp *http.Response, response []byte) ([]byte, error) { - if resp.StatusCode < 400 { - return response, nil - } - - logrus.Errorf("Received invalid status code %d:\n%s", resp.StatusCode, response) - - content := errors.New(string(response)) - - statusCodeErrorMap := map[int]error{ - 400: request_errors.BadRequest(content), - 401: request_errors.Unauthorized(content), - 403: request_errors.Forbidden(content), - 404: request_errors.NotFound(content), - 500: request_errors.ServerError(content), - } - - mappedError := statusCodeErrorMap[resp.StatusCode] - - if mappedError == nil { - return nil, request_errors.InvalidStatusCodeError(content) - } - - return nil, mappedError -} - -// Request builds and executues a request to the API -func (requestor *AuroraRequestor) Request(relativeURL string, method string, body []byte) ([]byte, error) { - req, err := requestor.buildRequest(relativeURL, method, body) - - client := http.Client{Timeout: 30 * time.Second} - resp, err := client.Do(req) - if err != nil { - logrus.Errorf("Failed request: %s", err) - return nil, err - } - - defer resp.Body.Close() - - rawResponse, err := httputil.DumpResponse(resp, true) - logrus.Debugf("Received raw response:\n%s", rawResponse) - if err != nil { - logrus.Errorf("Failed to dump response: %s", err) - } - - response, err := ioutil.ReadAll(resp.Body) - if err != nil { - logrus.Errorf("Failed to read response: %s", response) - return nil, err - } - - response, err = requestor.testInvalidResponse(resp, response) - if err != nil { - return nil, err - } - - return response, nil -} diff --git a/vendor/github.com/edeckers/auroradnsclient/tokens/generator.go b/vendor/github.com/edeckers/auroradnsclient/tokens/generator.go deleted file mode 100644 index bb47c25b3..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/tokens/generator.go +++ /dev/null @@ -1,35 +0,0 @@ -package tokens - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "fmt" - "strings" - "time" - - "github.com/sirupsen/logrus" -) - -// NewToken generates a token for accessing a specific method of the API -func NewToken(userID string, key string, method string, action string, timestamp time.Time) string { - fmtTime := timestamp.Format("20060102T150405Z") - logrus.Debugf("Built timestamp: %s", fmtTime) - - message := strings.Join([]string{method, action, fmtTime}, "") - logrus.Debugf("Built message: %s", message) - - signatureHmac := hmac.New(sha256.New, []byte(key)) - - signatureHmac.Write([]byte(message)) - - signature := base64.StdEncoding.EncodeToString([]byte(signatureHmac.Sum(nil))) - logrus.Debugf("Built signature: %s", signature) - - userIDAndSignature := fmt.Sprintf("%s:%s", userID, signature) - - token := base64.StdEncoding.EncodeToString([]byte(userIDAndSignature)) - logrus.Debugf("Built token: %s", token) - - return token -} diff --git a/vendor/github.com/edeckers/auroradnsclient/zones.go b/vendor/github.com/edeckers/auroradnsclient/zones.go deleted file mode 100644 index 49f3ce5ae..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/zones.go +++ /dev/null @@ -1,29 +0,0 @@ -package auroradnsclient - -import ( - "encoding/json" - - "github.com/edeckers/auroradnsclient/zones" - "github.com/sirupsen/logrus" -) - -// GetZones returns a list of all zones -func (client *AuroraDNSClient) GetZones() ([]zones.ZoneRecord, error) { - logrus.Debugf("GetZones") - response, err := client.requestor.Request("zones", "GET", []byte("")) - - if err != nil { - logrus.Errorf("Failed to get zones: %s", err) - return nil, err - } - - var respData []zones.ZoneRecord - err = json.Unmarshal(response, &respData) - if err != nil { - logrus.Errorf("Failed to unmarshall response: %s", err) - return nil, err - } - - logrus.Debugf("Unmarshalled response: %+v", respData) - return respData, nil -} diff --git a/vendor/github.com/edeckers/auroradnsclient/zones/datatypes.go b/vendor/github.com/edeckers/auroradnsclient/zones/datatypes.go deleted file mode 100644 index 16841ec57..000000000 --- a/vendor/github.com/edeckers/auroradnsclient/zones/datatypes.go +++ /dev/null @@ -1,7 +0,0 @@ -package zones - -// ZoneRecord describes the json format for a zone -type ZoneRecord struct { - ID string `json:"id"` - Name string `json:"name"` -} diff --git a/vendor/github.com/edeckers/auroradnsclient/LICENSE b/vendor/github.com/ldez/go-auroradns/LICENSE similarity index 100% rename from vendor/github.com/edeckers/auroradnsclient/LICENSE rename to vendor/github.com/ldez/go-auroradns/LICENSE diff --git a/vendor/github.com/ldez/go-auroradns/auth.go b/vendor/github.com/ldez/go-auroradns/auth.go new file mode 100644 index 000000000..295c34c9b --- /dev/null +++ b/vendor/github.com/ldez/go-auroradns/auth.go @@ -0,0 +1,98 @@ +package auroradns + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "fmt" + "net/http" + "strings" + "time" +) + +// TokenTransport HTTP transport for API authentication +type TokenTransport struct { + userID string + key string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// NewTokenTransport Creates a new TokenTransport +func NewTokenTransport(userID, key string) (*TokenTransport, error) { + if userID == "" || key == "" { + return nil, fmt.Errorf("credentials missing") + } + + return &TokenTransport{userID: userID, key: key}, nil +} + +// RoundTrip executes a single HTTP transaction +func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) { + enrichedReq := &http.Request{} + *enrichedReq = *req + + enrichedReq.Header = make(http.Header, len(req.Header)) + for k, s := range req.Header { + enrichedReq.Header[k] = append([]string(nil), s...) + } + + if t.userID != "" && t.key != "" { + timestamp := time.Now().UTC() + + fmtTime := timestamp.Format("20060102T150405Z") + req.Header.Set("X-AuroraDNS-Date", fmtTime) + + token, err := newToken(t.userID, t.key, req.Method, req.URL.Path, timestamp) + if err == nil { + req.Header.Set("Authorization", fmt.Sprintf("AuroraDNSv1 %s", token)) + } + } + + return t.transport().RoundTrip(enrichedReq) +} + +// Wrap Wrap a HTTP client Transport with the TokenTransport +func (t *TokenTransport) Wrap(client *http.Client) *http.Client { + backup := client.Transport + t.Transport = backup + client.Transport = t + return client +} + +// Client Creates a new HTTP client +func (t *TokenTransport) Client() *http.Client { + return &http.Client{ + Transport: t, + Timeout: 30 * time.Second, + } +} + +func (t *TokenTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} + +// newToken generates a token for accessing a specific method of the API +func newToken(userID string, key string, method string, action string, timestamp time.Time) (string, error) { + fmtTime := timestamp.Format("20060102T150405Z") + message := strings.Join([]string{method, action, fmtTime}, "") + + signatureHmac := hmac.New(sha256.New, []byte(key)) + _, err := signatureHmac.Write([]byte(message)) + if err != nil { + return "", err + } + + signature := base64.StdEncoding.EncodeToString(signatureHmac.Sum(nil)) + + userIDAndSignature := fmt.Sprintf("%s:%s", userID, signature) + + token := base64.StdEncoding.EncodeToString([]byte(userIDAndSignature)) + + return token, nil +} diff --git a/vendor/github.com/ldez/go-auroradns/client.go b/vendor/github.com/ldez/go-auroradns/client.go new file mode 100644 index 000000000..4387b1a73 --- /dev/null +++ b/vendor/github.com/ldez/go-auroradns/client.go @@ -0,0 +1,144 @@ +package auroradns + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" +) + +const defaultBaseURL = "https://api.auroradns.eu" + +const ( + contentTypeHeader = "Content-Type" + contentTypeJSON = "application/json" +) + +// ErrorResponse A representation of an API error message. +type ErrorResponse struct { + ErrorCode string `json:"error"` + Message string `json:"errormsg"` +} + +func (e *ErrorResponse) Error() string { + return fmt.Sprintf("%s - %s", e.ErrorCode, e.Message) +} + +// Option Type of a client option +type Option func(*Client) error + +// Client The API client +type Client struct { + baseURL *url.URL + UserAgent string + httpClient *http.Client +} + +// NewClient Creates a new client +func NewClient(httpClient *http.Client, opts ...Option) (*Client, error) { + if httpClient == nil { + httpClient = http.DefaultClient + } + + baseURL, _ := url.Parse(defaultBaseURL) + + client := &Client{ + baseURL: baseURL, + httpClient: httpClient, + } + + for _, opt := range opts { + err := opt(client) + if err != nil { + return nil, err + } + } + + return client, nil +} + +func (c *Client) newRequest(method, resource string, body io.Reader) (*http.Request, error) { + u, err := c.baseURL.Parse(resource) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(method, u.String(), body) + if err != nil { + return nil, err + } + + req.Header.Set(contentTypeHeader, contentTypeJSON) + + if c.UserAgent != "" { + req.Header.Set("User-Agent", c.UserAgent) + } + + return req, nil +} + +func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + + if err = checkResponse(resp); err != nil { + return resp, err + } + + if v == nil { + return resp, nil + } + + raw, err := ioutil.ReadAll(resp.Body) + if err != nil { + return resp, fmt.Errorf("failed to read body: %v", err) + } + + if err = json.Unmarshal(raw, v); err != nil { + return resp, fmt.Errorf("unmarshaling %T error: %v: %s", err, v, string(raw)) + } + + return resp, nil +} + +func checkResponse(resp *http.Response) error { + if c := resp.StatusCode; 200 <= c && c <= 299 { + return nil + } + + data, err := ioutil.ReadAll(resp.Body) + if err == nil && data != nil { + errorResponse := new(ErrorResponse) + err = json.Unmarshal(data, errorResponse) + if err != nil { + return fmt.Errorf("unmarshaling ErrorResponse error: %v: %s", err.Error(), string(data)) + } + + return errorResponse + } + defer func() { _ = resp.Body.Close() }() + + return nil +} + +// WithBaseURL Allows to define a custom base URL +func WithBaseURL(rawBaseURL string) func(*Client) error { + return func(client *Client) error { + if len(rawBaseURL) == 0 { + return nil + } + + baseURL, err := url.Parse(rawBaseURL) + if err != nil { + return err + } + + client.baseURL = baseURL + return nil + } +} diff --git a/vendor/github.com/ldez/go-auroradns/records.go b/vendor/github.com/ldez/go-auroradns/records.go new file mode 100644 index 000000000..a1cf08717 --- /dev/null +++ b/vendor/github.com/ldez/go-auroradns/records.go @@ -0,0 +1,91 @@ +package auroradns + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +// Record types +const ( + RecordTypeA = "A" + RecordTypeAAAA = "AAAA" + RecordTypeCNAME = "CNAME" + RecordTypeMX = "MX" + RecordTypeNS = "NS" + RecordTypeSOA = "SOA" + RecordTypeSRV = "SRV" + RecordTypeTXT = "TXT" + RecordTypeDS = "DS" + RecordTypePTR = "PTR" + RecordTypeSSHFP = "SSHFP" + RecordTypeTLSA = "TLS" +) + +// Record a DNS record +type Record struct { + ID string `json:"id,omitempty"` + RecordType string `json:"type"` + Name string `json:"name"` + Content string `json:"content"` + TTL int `json:"ttl,omitempty"` +} + +// CreateRecord Creates a new record. +func (c *Client) CreateRecord(zoneID string, record Record) (*Record, *http.Response, error) { + body, err := json.Marshal(record) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshall request body: %v", err) + } + + resource := fmt.Sprintf("/zones/%s/records", zoneID) + + req, err := c.newRequest(http.MethodPost, resource, bytes.NewReader(body)) + if err != nil { + return nil, nil, err + } + + newRecord := new(Record) + resp, err := c.do(req, newRecord) + if err != nil { + return nil, resp, err + } + + return newRecord, resp, nil +} + +// DeleteRecord Delete a record. +func (c *Client) DeleteRecord(zoneID string, recordID string) (bool, *http.Response, error) { + resource := fmt.Sprintf("/zones/%s/records/%s", zoneID, recordID) + + req, err := c.newRequest(http.MethodDelete, resource, nil) + if err != nil { + return false, nil, err + } + + resp, err := c.do(req, nil) + if err != nil { + return false, resp, err + } + + return true, resp, nil +} + +// ListRecords returns a list of all records in given zone +func (c *Client) ListRecords(zoneID string) ([]Record, *http.Response, error) { + resource := fmt.Sprintf("/zones/%s/records", zoneID) + + req, err := c.newRequest(http.MethodGet, resource, nil) + if err != nil { + return nil, nil, err + } + + var records []Record + resp, err := c.do(req, &records) + if err != nil { + return nil, resp, err + } + + return records, resp, nil +} diff --git a/vendor/github.com/ldez/go-auroradns/zones.go b/vendor/github.com/ldez/go-auroradns/zones.go new file mode 100644 index 000000000..8e372188f --- /dev/null +++ b/vendor/github.com/ldez/go-auroradns/zones.go @@ -0,0 +1,69 @@ +package auroradns + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +// Zone a DNS zone +type Zone struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` +} + +// CreateZone Creates a zone. +func (c *Client) CreateZone(domain string) (*Zone, *http.Response, error) { + body, err := json.Marshal(Zone{Name: domain}) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshall request body: %v", err) + } + + req, err := c.newRequest(http.MethodPost, "/zones", bytes.NewReader(body)) + if err != nil { + return nil, nil, err + } + + zone := new(Zone) + resp, err := c.do(req, zone) + if err != nil { + return nil, resp, err + } + + return zone, resp, nil +} + +// DeleteZone Delete a zone. +func (c *Client) DeleteZone(zoneID string) (bool, *http.Response, error) { + resource := fmt.Sprintf("/zones/%s", zoneID) + + req, err := c.newRequest(http.MethodDelete, resource, nil) + if err != nil { + return false, nil, err + } + + resp, err := c.do(req, nil) + if err != nil { + return false, resp, err + } + + return true, resp, nil + +} + +// ListZones returns a list of all zones. +func (c *Client) ListZones() ([]Zone, *http.Response, error) { + req, err := c.newRequest(http.MethodGet, "/zones", nil) + if err != nil { + return nil, nil, err + } + + var zones []Zone + resp, err := c.do(req, &zones) + if err != nil { + return nil, resp, err + } + + return zones, resp, nil +} diff --git a/vendor/github.com/xenolf/lego/acme/dns_challenge.go b/vendor/github.com/xenolf/lego/acme/dns_challenge.go index f803d0a8c..d9c252e7e 100644 --- a/vendor/github.com/xenolf/lego/acme/dns_challenge.go +++ b/vendor/github.com/xenolf/lego/acme/dns_challenge.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "strings" + "sync" "time" "github.com/miekg/dns" @@ -18,8 +19,9 @@ type preCheckDNSFunc func(fqdn, value string) (bool, error) var ( // PreCheckDNS checks DNS propagation before notifying ACME that // the DNS challenge is ready. - PreCheckDNS preCheckDNSFunc = checkDNSPropagation - fqdnToZone = map[string]string{} + PreCheckDNS preCheckDNSFunc = checkDNSPropagation + fqdnToZone = map[string]string{} + muFqdnToZone sync.Mutex ) const defaultResolvConf = "/etc/resolv.conf" @@ -262,6 +264,9 @@ func lookupNameservers(fqdn string) ([]string, error) { // FindZoneByFqdn determines the zone apex for the given fqdn by recursing up the // domain labels until the nameserver returns a SOA record in the answer section. func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) { + muFqdnToZone.Lock() + defer muFqdnToZone.Unlock() + // Do we have it cached? if zone, ok := fqdnToZone[fqdn]; ok { return zone, nil diff --git a/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go index d3cb76da9..21e455c90 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go @@ -1,3 +1,4 @@ +// Package auroradns implements a DNS provider for solving the DNS-01 challenge using Aurora DNS. package auroradns import ( @@ -6,9 +7,7 @@ import ( "sync" "time" - "github.com/edeckers/auroradnsclient" - "github.com/edeckers/auroradnsclient/records" - "github.com/edeckers/auroradnsclient/zones" + "github.com/ldez/go-auroradns" "github.com/xenolf/lego/acme" "github.com/xenolf/lego/platform/config/env" ) @@ -39,7 +38,7 @@ type DNSProvider struct { recordIDs map[string]string recordIDsMu sync.Mutex config *Config - client *auroradnsclient.AuroraDNSClient + client *auroradns.Client } // NewDNSProvider returns a DNSProvider instance configured for AuroraDNS. @@ -85,7 +84,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config.BaseURL = defaultBaseURL } - client, err := auroradnsclient.NewAuroraDNSClient(config.BaseURL, config.UserID, config.Key) + tr, err := auroradns.NewTokenTransport(config.UserID, config.Key) + if err != nil { + return nil, fmt.Errorf("aurora: %v", err) + } + + client, err := auroradns.NewClient(tr.Client(), auroradns.WithBaseURL(config.BaseURL)) if err != nil { return nil, fmt.Errorf("aurora: %v", err) } @@ -117,26 +121,25 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { authZone = acme.UnFqdn(authZone) - zoneRecord, err := d.getZoneInformationByName(authZone) + zone, err := d.getZoneInformationByName(authZone) if err != nil { return fmt.Errorf("aurora: could not create record: %v", err) } - reqData := - records.CreateRecordRequest{ - RecordType: "TXT", - Name: subdomain, - Content: value, - TTL: d.config.TTL, - } + record := auroradns.Record{ + RecordType: "TXT", + Name: subdomain, + Content: value, + TTL: d.config.TTL, + } - respData, err := d.client.CreateRecord(zoneRecord.ID, reqData) + newRecord, _, err := d.client.CreateRecord(zone.ID, record) if err != nil { return fmt.Errorf("aurora: could not create record: %v", err) } d.recordIDsMu.Lock() - d.recordIDs[fqdn] = respData.ID + d.recordIDs[fqdn] = newRecord.ID d.recordIDsMu.Unlock() return nil @@ -161,12 +164,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { authZone = acme.UnFqdn(authZone) - zoneRecord, err := d.getZoneInformationByName(authZone) + zone, err := d.getZoneInformationByName(authZone) if err != nil { return err } - _, err = d.client.RemoveRecord(zoneRecord.ID, recordID) + _, _, err = d.client.DeleteRecord(zone.ID, recordID) if err != nil { return err } @@ -184,10 +187,10 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRecord, error) { - zs, err := d.client.GetZones() +func (d *DNSProvider) getZoneInformationByName(name string) (auroradns.Zone, error) { + zs, _, err := d.client.ListZones() if err != nil { - return zones.ZoneRecord{}, err + return auroradns.Zone{}, err } for _, element := range zs { @@ -196,5 +199,5 @@ func (d *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRecord, e } } - return zones.ZoneRecord{}, fmt.Errorf("could not find Zone record") + return auroradns.Zone{}, fmt.Errorf("could not find Zone record") } diff --git a/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go b/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go index d04c3d668..74a4e531f 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go +++ b/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "io/ioutil" "net/http" "strings" "time" @@ -15,18 +16,26 @@ import ( "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/Azure/go-autorest/autorest/to" "github.com/xenolf/lego/acme" "github.com/xenolf/lego/platform/config/env" ) +const defaultMetadataEndpoint = "http://169.254.169.254" + // Config is used to configure the creation of the DNSProvider type Config struct { - ClientID string - ClientSecret string - SubscriptionID string - TenantID string - ResourceGroup string + // optional if using instance metadata service + ClientID string + ClientSecret string + TenantID string + + SubscriptionID string + ResourceGroup string + + MetadataEndpoint string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -39,29 +48,26 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt("AZURE_TTL", 60), PropagationTimeout: env.GetOrDefaultSecond("AZURE_PROPAGATION_TIMEOUT", 2*time.Minute), PollingInterval: env.GetOrDefaultSecond("AZURE_POLLING_INTERVAL", 2*time.Second), + MetadataEndpoint: env.GetOrFile("AZURE_METADATA_ENDPOINT"), } } // DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { - config *Config + config *Config + authorizer autorest.Authorizer } // NewDNSProvider returns a DNSProvider instance configured for azure. -// Credentials must be passed in the environment variables: AZURE_CLIENT_ID, -// AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP +// Credentials can be passed in the environment variables: +// AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP +// If the credentials are _not_ set via the environment, +// then it will attempt to get a bearer token via the instance metadata service. +// see: https://github.com/Azure/go-autorest/blob/v10.14.0/autorest/azure/auth/auth.go#L38-L42 func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get("AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP") - if err != nil { - return nil, fmt.Errorf("azure: %v", err) - } - config := NewDefaultConfig() - config.ClientID = values["AZURE_CLIENT_ID"] - config.ClientSecret = values["AZURE_CLIENT_SECRET"] - config.SubscriptionID = values["AZURE_SUBSCRIPTION_ID"] - config.TenantID = values["AZURE_TENANT_ID"] - config.ResourceGroup = values["AZURE_RESOURCE_GROUP"] + config.SubscriptionID = env.GetOrFile("AZURE_SUBSCRIPTION_ID") + config.ResourceGroup = env.GetOrFile("AZURE_RESOURCE_GROUP") return NewDNSProviderConfig(config) } @@ -73,8 +79,8 @@ func NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, config := NewDefaultConfig() config.ClientID = clientID config.ClientSecret = clientSecret - config.SubscriptionID = subscriptionID config.TenantID = tenantID + config.SubscriptionID = subscriptionID config.ResourceGroup = resourceGroup return NewDNSProviderConfig(config) @@ -86,11 +92,40 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("azure: the configuration of the DNS provider is nil") } - if config.ClientID == "" || config.ClientSecret == "" || config.SubscriptionID == "" || config.TenantID == "" || config.ResourceGroup == "" { - return nil, errors.New("azure: some credentials information are missing") + if config.HTTPClient == nil { + config.HTTPClient = http.DefaultClient } - return &DNSProvider{config: config}, nil + authorizer, err := getAuthorizer(config) + if err != nil { + return nil, err + } + + if config.SubscriptionID == "" { + subsID, err := getMetadata(config, "subscriptionId") + if err != nil { + return nil, fmt.Errorf("azure: %v", err) + } + + if subsID == "" { + return nil, errors.New("azure: SubscriptionID is missing") + } + config.SubscriptionID = subsID + } + + if config.ResourceGroup == "" { + resGroup, err := getMetadata(config, "resourceGroupName") + if err != nil { + return nil, fmt.Errorf("azure: %v", err) + } + + if resGroup == "" { + return nil, errors.New("azure: ResourceGroup is missing") + } + config.ResourceGroup = resGroup + } + + return &DNSProvider{config: config, authorizer: authorizer}, nil } // Timeout returns the timeout and interval to use when checking for DNS @@ -110,12 +145,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } rsc := dns.NewRecordSetsClient(d.config.SubscriptionID) - spt, err := d.newServicePrincipalToken(azure.PublicCloud.ResourceManagerEndpoint) - if err != nil { - return fmt.Errorf("azure: %v", err) - } - - rsc.Authorizer = autorest.NewBearerAuthorizer(spt) + rsc.Authorizer = d.authorizer relative := toRelativeRecord(fqdn, acme.ToFqdn(zone)) rec := dns.RecordSet{ @@ -145,12 +175,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { relative := toRelativeRecord(fqdn, acme.ToFqdn(zone)) rsc := dns.NewRecordSetsClient(d.config.SubscriptionID) - spt, err := d.newServicePrincipalToken(azure.PublicCloud.ResourceManagerEndpoint) - if err != nil { - return fmt.Errorf("azure: %v", err) - } - - rsc.Authorizer = autorest.NewBearerAuthorizer(spt) + rsc.Authorizer = d.authorizer _, err = rsc.Delete(ctx, d.config.ResourceGroup, zone, relative, dns.TXT, "") if err != nil { @@ -166,14 +191,8 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, return "", err } - // Now we want to to Azure and get the zone. - spt, err := d.newServicePrincipalToken(azure.PublicCloud.ResourceManagerEndpoint) - if err != nil { - return "", err - } - dc := dns.NewZonesClient(d.config.SubscriptionID) - dc.Authorizer = autorest.NewBearerAuthorizer(spt) + dc.Authorizer = d.authorizer zone, err := dc.Get(ctx, d.config.ResourceGroup, acme.UnFqdn(authZone)) if err != nil { @@ -184,17 +203,61 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, return to.String(zone.Name), nil } -// NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the -// passed credentials map. -func (d *DNSProvider) newServicePrincipalToken(scope string) (*adal.ServicePrincipalToken, error) { - oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, d.config.TenantID) - if err != nil { - return nil, err - } - return adal.NewServicePrincipalToken(*oauthConfig, d.config.ClientID, d.config.ClientSecret, scope) -} - // Returns the relative record to the domain func toRelativeRecord(domain, zone string) string { return acme.UnFqdn(strings.TrimSuffix(domain, zone)) } + +func getAuthorizer(config *Config) (autorest.Authorizer, error) { + if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" { + oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, config.TenantID) + if err != nil { + return nil, err + } + + spt, err := adal.NewServicePrincipalToken(*oauthConfig, config.ClientID, config.ClientSecret, azure.PublicCloud.ResourceManagerEndpoint) + if err != nil { + return nil, err + } + + spt.SetSender(config.HTTPClient) + return autorest.NewBearerAuthorizer(spt), nil + } + + return auth.NewAuthorizerFromEnvironment() +} + +// Fetches metadata from environment or he instance metadata service +// borrowed from https://github.com/Microsoft/azureimds/blob/master/imdssample.go +func getMetadata(config *Config, field string) (string, error) { + metadataEndpoint := config.MetadataEndpoint + if len(metadataEndpoint) == 0 { + metadataEndpoint = defaultMetadataEndpoint + } + + resource := fmt.Sprintf("%s/metadata/instance/compute/%s", metadataEndpoint, field) + req, err := http.NewRequest(http.MethodGet, resource, nil) + if err != nil { + return "", err + } + + req.Header.Add("Metadata", "True") + + q := req.URL.Query() + q.Add("format", "text") + q.Add("api-version", "2017-12-01") + req.URL.RawQuery = q.Encode() + + resp, err := config.HTTPClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(respBody[:]), nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go index ba0727b9e..95f2dda05 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/client.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io/ioutil" "net/http" "time" ) @@ -27,6 +28,10 @@ type Record struct { SourceID int `json:"sourceId"` } +type recordsResponse struct { + Records *[]Record `json:"data"` +} + // Client DNSMadeEasy client type Client struct { apiKey string @@ -82,10 +87,6 @@ func (c *Client) GetRecords(domain *Domain, recordName, recordType string) (*[]R } defer resp.Body.Close() - type recordsResponse struct { - Records *[]Record `json:"data"` - } - records := &recordsResponse{} err = json.NewDecoder(resp.Body).Decode(&records) if err != nil { @@ -151,7 +152,11 @@ func (c *Client) sendRequest(method, resource string, payload interface{}) (*htt } if resp.StatusCode > 299 { - return nil, fmt.Errorf("DNSMadeEasy API request failed with HTTP status code %d", resp.StatusCode) + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode) + } + return nil, fmt.Errorf("request failed with HTTP status code %d: %s", resp.StatusCode, string(body)) } return resp, nil diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go index 519a44519..a55ea3978 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -1,3 +1,4 @@ +// Package dnsmadeeasy implements a DNS provider for solving the DNS-01 challenge using DNS Made Easy. package dnsmadeeasy import ( @@ -112,13 +113,13 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error { authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { - return err + return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err) } // fetch the domain details domain, err := d.client.GetDomain(authZone) if err != nil { - return err + return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err) } // create the TXT record @@ -126,7 +127,10 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error { record := &Record{Type: "TXT", Name: name, Value: value, TTL: d.config.TTL} err = d.client.CreateRecord(domain, record) - return err + if err != nil { + return fmt.Errorf("dnsmadeeasy: unable to create record for %s: %v", name, err) + } + return nil } // CleanUp removes the TXT records matching the specified parameters @@ -135,31 +139,32 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { - return err + return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err) } // fetch the domain details domain, err := d.client.GetDomain(authZone) if err != nil { - return err + return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err) } // find matching records name := strings.Replace(fqdn, "."+authZone, "", 1) records, err := d.client.GetRecords(domain, name, "TXT") if err != nil { - return err + return fmt.Errorf("dnsmadeeasy: unable to get records for domain %s: %v", domain.Name, err) } // delete records + var lastError error for _, record := range *records { err = d.client.DeleteRecord(record) if err != nil { - return err + lastError = fmt.Errorf("dnsmadeeasy: unable to delete record [id=%d, name=%s]: %v", record.ID, record.Name, err) } } - return nil + return lastError } // Timeout returns the timeout and interval to use when checking for DNS propagation. diff --git a/vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go b/vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go index cc0e36338..9edd27b72 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go @@ -1,4 +1,4 @@ -// Package dreamhost Adds lego support for http://dreamhost.com DNS updates +// Package dreamhost implements a DNS provider for solving the DNS-01 challenge using DreamHost. // See https://help.dreamhost.com/hc/en-us/articles/217560167-API_overview // and https://help.dreamhost.com/hc/en-us/articles/217555707-DNS-API-commands for the API spec. package dreamhost diff --git a/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go b/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go index 6bbf76352..7581af173 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go @@ -1,4 +1,4 @@ -// Package duckdns Adds lego support for http://duckdns.org. +// Package duckdns implements a DNS provider for solving the DNS-01 challenge using DuckDNS. // See http://www.duckdns.org/spec.jsp for more info on updating TXT records. package duckdns @@ -7,8 +7,12 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" + "strconv" + "strings" "time" + "github.com/miekg/dns" "github.com/xenolf/lego/acme" "github.com/xenolf/lego/platform/config/env" ) @@ -96,9 +100,16 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // To update the TXT record we just need to make one simple get request. // In DuckDNS you only have one TXT record shared with the domain and all sub domains. func updateTxtRecord(domain, token, txt string, clear bool) error { - u := fmt.Sprintf("https://www.duckdns.org/update?domains=%s&token=%s&clear=%t&txt=%s", domain, token, clear, txt) + u, _ := url.Parse("https://www.duckdns.org/update") - response, err := acme.HTTPClient.Get(u) + query := u.Query() + query.Set("domains", getMainDomain(domain)) + query.Set("token", token) + query.Set("clear", strconv.FormatBool(clear)) + query.Set("txt", txt) + u.RawQuery = query.Encode() + + response, err := acme.HTTPClient.Get(u.String()) if err != nil { return err } @@ -115,3 +126,23 @@ func updateTxtRecord(domain, token, txt string, clear bool) error { } return nil } + +// DuckDNS only lets you write to your subdomain +// so it must be in format subdomain.duckdns.org +// not in format subsubdomain.subdomain.duckdns.org +// so strip off everything that is not top 3 levels +func getMainDomain(domain string) string { + domain = acme.UnFqdn(domain) + + split := dns.Split(domain) + if strings.HasSuffix(strings.ToLower(domain), "duckdns.org") { + if len(split) < 3 { + return "" + } + + firstSubDomainIndex := split[len(split)-3] + return domain[firstSubDomainIndex:] + } + + return domain[split[len(split)-1]:] +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/netcup/client.go b/vendor/github.com/xenolf/lego/providers/dns/netcup/client.go index f30bd7f12..17a2ae968 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/netcup/client.go +++ b/vendor/github.com/xenolf/lego/providers/dns/netcup/client.go @@ -25,27 +25,27 @@ type Request struct { Param interface{} `json:"param"` } -// LoginMsg as specified in netcup WSDL +// LoginRequest as specified in netcup WSDL // https://ccp.netcup.net/run/webservice/servers/endpoint.php#login -type LoginMsg struct { +type LoginRequest struct { CustomerNumber string `json:"customernumber"` APIKey string `json:"apikey"` APIPassword string `json:"apipassword"` ClientRequestID string `json:"clientrequestid,omitempty"` } -// LogoutMsg as specified in netcup WSDL +// LogoutRequest as specified in netcup WSDL // https://ccp.netcup.net/run/webservice/servers/endpoint.php#logout -type LogoutMsg struct { +type LogoutRequest struct { CustomerNumber string `json:"customernumber"` APIKey string `json:"apikey"` APISessionID string `json:"apisessionid"` ClientRequestID string `json:"clientrequestid,omitempty"` } -// UpdateDNSRecordsMsg as specified in netcup WSDL +// UpdateDNSRecordsRequest as specified in netcup WSDL // https://ccp.netcup.net/run/webservice/servers/endpoint.php#updateDnsRecords -type UpdateDNSRecordsMsg struct { +type UpdateDNSRecordsRequest struct { DomainName string `json:"domainname"` CustomerNumber string `json:"customernumber"` APIKey string `json:"apikey"` @@ -55,15 +55,15 @@ type UpdateDNSRecordsMsg struct { } // DNSRecordSet as specified in netcup WSDL -// needed in UpdateDNSRecordsMsg +// needed in UpdateDNSRecordsRequest // https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecordset type DNSRecordSet struct { DNSRecords []DNSRecord `json:"dnsrecords"` } -// InfoDNSRecordsMsg as specified in netcup WSDL +// InfoDNSRecordsRequest as specified in netcup WSDL // https://ccp.netcup.net/run/webservice/servers/endpoint.php#infoDnsRecords -type InfoDNSRecordsMsg struct { +type InfoDNSRecordsRequest struct { DomainName string `json:"domainname"` CustomerNumber string `json:"customernumber"` APIKey string `json:"apikey"` @@ -87,33 +87,30 @@ type DNSRecord struct { // ResponseMsg as specified in netcup WSDL // https://ccp.netcup.net/run/webservice/servers/endpoint.php#Responsemessage type ResponseMsg struct { - ServerRequestID string `json:"serverrequestid"` - ClientRequestID string `json:"clientrequestid,omitempty"` - Action string `json:"action"` - Status string `json:"status"` - StatusCode int `json:"statuscode"` - ShortMessage string `json:"shortmessage"` - LongMessage string `json:"longmessage"` - ResponseData ResponseData `json:"responsedata,omitempty"` + ServerRequestID string `json:"serverrequestid"` + ClientRequestID string `json:"clientrequestid,omitempty"` + Action string `json:"action"` + Status string `json:"status"` + StatusCode int `json:"statuscode"` + ShortMessage string `json:"shortmessage"` + LongMessage string `json:"longmessage"` + ResponseData json.RawMessage `json:"responsedata,omitempty"` } -// LogoutResponseMsg similar to ResponseMsg -// allows empty ResponseData field whilst unmarshaling -type LogoutResponseMsg struct { - ServerRequestID string `json:"serverrequestid"` - ClientRequestID string `json:"clientrequestid,omitempty"` - Action string `json:"action"` - Status string `json:"status"` - StatusCode int `json:"statuscode"` - ShortMessage string `json:"shortmessage"` - LongMessage string `json:"longmessage"` - ResponseData string `json:"responsedata,omitempty"` +func (r *ResponseMsg) Error() string { + return fmt.Sprintf("an error occurred during the action %s: [Status=%s, StatusCode=%d, ShortMessage=%s, LongMessage=%s]", + r.Action, r.Status, r.StatusCode, r.ShortMessage, r.LongMessage) } -// ResponseData to enable correct unmarshaling of ResponseMsg -type ResponseData struct { +// LoginResponse response to login action. +type LoginResponse struct { + APISessionID string `json:"apisessionid"` +} + +// InfoDNSRecordsResponse response to infoDnsRecords action. +type InfoDNSRecordsResponse struct { APISessionID string `json:"apisessionid"` - DNSRecords []DNSRecord `json:"dnsrecords"` + DNSRecords []DNSRecord `json:"dnsrecords,omitempty"` } // Client netcup DNS client @@ -126,7 +123,11 @@ type Client struct { } // NewClient creates a netcup DNS client -func NewClient(customerNumber string, apiKey string, apiPassword string) *Client { +func NewClient(customerNumber string, apiKey string, apiPassword string) (*Client, error) { + if customerNumber == "" || apiKey == "" || apiPassword == "" { + return nil, fmt.Errorf("credentials missing") + } + return &Client{ customerNumber: customerNumber, apiKey: apiKey, @@ -135,7 +136,7 @@ func NewClient(customerNumber string, apiKey string, apiPassword string) *Client HTTPClient: &http.Client{ Timeout: 10 * time.Second, }, - } + }, nil } // Login performs the login as specified by the netcup WSDL @@ -144,7 +145,7 @@ func NewClient(customerNumber string, apiKey string, apiPassword string) *Client func (c *Client) Login() (string, error) { payload := &Request{ Action: "login", - Param: &LoginMsg{ + Param: &LoginRequest{ CustomerNumber: c.customerNumber, APIKey: c.apiKey, APIPassword: c.apiPassword, @@ -152,21 +153,13 @@ func (c *Client) Login() (string, error) { }, } - response, err := c.sendRequest(payload) + var responseData LoginResponse + err := c.doRequest(payload, &responseData) if err != nil { - return "", fmt.Errorf("error sending request to DNS-API, %v", err) + return "", fmt.Errorf("loging error: %v", err) } - var r ResponseMsg - - err = json.Unmarshal(response, &r) - if err != nil { - return "", fmt.Errorf("error decoding response of DNS-API, %v", err) - } - if r.Status != success { - return "", fmt.Errorf("error logging into DNS-API, %v", r.LongMessage) - } - return r.ResponseData.APISessionID, nil + return responseData.APISessionID, nil } // Logout performs the logout with the supplied sessionID as specified by the netcup WSDL @@ -174,7 +167,7 @@ func (c *Client) Login() (string, error) { func (c *Client) Logout(sessionID string) error { payload := &Request{ Action: "logout", - Param: &LogoutMsg{ + Param: &LogoutRequest{ CustomerNumber: c.customerNumber, APIKey: c.apiKey, APISessionID: sessionID, @@ -182,54 +175,34 @@ func (c *Client) Logout(sessionID string) error { }, } - response, err := c.sendRequest(payload) + err := c.doRequest(payload, nil) if err != nil { - return fmt.Errorf("error logging out of DNS-API: %v", err) + return fmt.Errorf("logout error: %v", err) } - var r LogoutResponseMsg - - err = json.Unmarshal(response, &r) - if err != nil { - return fmt.Errorf("error logging out of DNS-API: %v", err) - } - - if r.Status != success { - return fmt.Errorf("error logging out of DNS-API: %v", r.ShortMessage) - } return nil } // UpdateDNSRecord performs an update of the DNSRecords as specified by the netcup WSDL // https://ccp.netcup.net/run/webservice/servers/endpoint.php -func (c *Client) UpdateDNSRecord(sessionID, domainName string, record DNSRecord) error { +func (c *Client) UpdateDNSRecord(sessionID, domainName string, records []DNSRecord) error { payload := &Request{ Action: "updateDnsRecords", - Param: UpdateDNSRecordsMsg{ + Param: UpdateDNSRecordsRequest{ DomainName: domainName, CustomerNumber: c.customerNumber, APIKey: c.apiKey, APISessionID: sessionID, ClientRequestID: "", - DNSRecordSet: DNSRecordSet{DNSRecords: []DNSRecord{record}}, + DNSRecordSet: DNSRecordSet{DNSRecords: records}, }, } - response, err := c.sendRequest(payload) + err := c.doRequest(payload, nil) if err != nil { - return err + return fmt.Errorf("error when sending the request: %v", err) } - var r ResponseMsg - - err = json.Unmarshal(response, &r) - if err != nil { - return err - } - - if r.Status != success { - return fmt.Errorf("%s: %+v", r.ShortMessage, r) - } return nil } @@ -239,7 +212,7 @@ func (c *Client) UpdateDNSRecord(sessionID, domainName string, record DNSRecord) func (c *Client) GetDNSRecords(hostname, apiSessionID string) ([]DNSRecord, error) { payload := &Request{ Action: "infoDnsRecords", - Param: InfoDNSRecordsMsg{ + Param: InfoDNSRecordsRequest{ DomainName: hostname, CustomerNumber: c.customerNumber, APIKey: c.apiKey, @@ -248,82 +221,98 @@ func (c *Client) GetDNSRecords(hostname, apiSessionID string) ([]DNSRecord, erro }, } - response, err := c.sendRequest(payload) + var responseData InfoDNSRecordsResponse + err := c.doRequest(payload, &responseData) if err != nil { - return nil, err + return nil, fmt.Errorf("error when sending the request: %v", err) } - var r ResponseMsg - - err = json.Unmarshal(response, &r) - if err != nil { - return nil, err - } - - if r.Status != success { - return nil, fmt.Errorf("%s", r.ShortMessage) - } - return r.ResponseData.DNSRecords, nil + return responseData.DNSRecords, nil } -// sendRequest marshals given body to JSON, send the request to netcup API +// doRequest marshals given body to JSON, send the request to netcup API // and returns body of response -func (c *Client) sendRequest(payload interface{}) ([]byte, error) { +func (c *Client) doRequest(payload interface{}, responseData interface{}) error { body, err := json.Marshal(payload) if err != nil { - return nil, err + return err } req, err := http.NewRequest(http.MethodPost, c.BaseURL, bytes.NewReader(body)) if err != nil { - return nil, err + return err } - req.Close = true + req.Close = true req.Header.Set("content-type", "application/json") req.Header.Set("User-Agent", acme.UserAgent) resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, err + return err } - if resp.StatusCode > 299 { - return nil, fmt.Errorf("API request failed with HTTP Status code %d", resp.StatusCode) + if err = checkResponse(resp); err != nil { + return err } - body, err = ioutil.ReadAll(resp.Body) + respMsg, err := decodeResponseMsg(resp) if err != nil { - return nil, fmt.Errorf("read of response body failed, %v", err) + return err } - defer resp.Body.Close() - return body, nil -} + if respMsg.Status != success { + return respMsg + } -// GetDNSRecordIdx searches a given array of DNSRecords for a given DNSRecord -// equivalence is determined by Destination and RecortType attributes -// returns index of given DNSRecord in given array of DNSRecords -func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) { - for index, element := range records { - if record.Destination == element.Destination && record.RecordType == element.RecordType { - return index, nil + if responseData != nil { + err = json.Unmarshal(respMsg.ResponseData, responseData) + if err != nil { + return fmt.Errorf("%v: unmarshaling %T error: %v: %s", + respMsg, responseData, err, string(respMsg.ResponseData)) } } - return -1, fmt.Errorf("no DNS Record found") + + return nil } -// CreateTxtRecord uses the supplied values to return a DNSRecord of type TXT for the dns-01 challenge -func CreateTxtRecord(hostname, value string, ttl int) DNSRecord { - return DNSRecord{ - ID: 0, - Hostname: hostname, - RecordType: "TXT", - Priority: "", - Destination: value, - DeleteRecord: false, - State: "", - TTL: ttl, +func checkResponse(resp *http.Response) error { + if resp.StatusCode > 299 { + if resp.Body == nil { + return fmt.Errorf("response body is nil, status code=%d", resp.StatusCode) + } + + defer resp.Body.Close() + + raw, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err) + } + + return fmt.Errorf("status code=%d: %s", resp.StatusCode, string(raw)) } + + return nil +} + +func decodeResponseMsg(resp *http.Response) (*ResponseMsg, error) { + if resp.Body == nil { + return nil, fmt.Errorf("response body is nil, status code=%d", resp.StatusCode) + } + + defer resp.Body.Close() + + raw, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err) + } + + var respMsg ResponseMsg + err = json.Unmarshal(raw, &respMsg) + if err != nil { + return nil, fmt.Errorf("unmarshaling %T error [status code=%d]: %v: %s", respMsg, resp.StatusCode, err, string(raw)) + } + + return &respMsg, nil } diff --git a/vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go b/vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go index 983b71e57..0dc59f960 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go +++ b/vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go @@ -9,6 +9,7 @@ import ( "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/log" "github.com/xenolf/lego/platform/config/env" ) @@ -27,8 +28,8 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt("NETCUP_TTL", 120), - PropagationTimeout: env.GetOrDefaultSecond("NETCUP_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond("NETCUP_POLLING_INTERVAL", acme.DefaultPollingInterval), + PropagationTimeout: env.GetOrDefaultSecond("NETCUP_PROPAGATION_TIMEOUT", 120*time.Second), + PollingInterval: env.GetOrDefaultSecond("NETCUP_POLLING_INTERVAL", 5*time.Second), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond("NETCUP_HTTP_TIMEOUT", 10*time.Second), }, @@ -76,11 +77,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("netcup: the configuration of the DNS provider is nil") } - if config.Customer == "" || config.Key == "" || config.Password == "" { - return nil, fmt.Errorf("netcup: netcup credentials missing") + client, err := NewClient(config.Customer, config.Key, config.Password) + if err != nil { + return nil, fmt.Errorf("netcup: %v", err) } - client := NewClient(config.Customer, config.Key, config.Password) client.HTTPClient = config.HTTPClient return &DNSProvider{client: client, config: config}, nil @@ -100,27 +101,37 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error { return fmt.Errorf("netcup: %v", err) } - hostname := strings.Replace(fqdn, "."+zone, "", 1) - record := CreateTxtRecord(hostname, value, d.config.TTL) - - err = d.client.UpdateDNSRecord(sessionID, acme.UnFqdn(zone), record) - if err != nil { - if errLogout := d.client.Logout(sessionID); errLogout != nil { - return fmt.Errorf("netcup: failed to add TXT-Record: %v; %v", err, errLogout) + defer func() { + err = d.client.Logout(sessionID) + if err != nil { + log.Print("netcup: %v", err) } + }() + + hostname := strings.Replace(fqdn, "."+zone, "", 1) + record := createTxtRecord(hostname, value, d.config.TTL) + + zone = acme.UnFqdn(zone) + + records, err := d.client.GetDNSRecords(zone, sessionID) + if err != nil { + // skip no existing records + log.Infof("no existing records, error ignored: %v", err) + } + + records = append(records, record) + + err = d.client.UpdateDNSRecord(sessionID, zone, records) + if err != nil { return fmt.Errorf("netcup: failed to add TXT-Record: %v", err) } - err = d.client.Logout(sessionID) - if err != nil { - return fmt.Errorf("netcup: %v", err) - } return nil } // CleanUp removes the TXT record matching the specified parameters -func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error { - fqdn, value, _ := acme.DNS01Record(domainname, keyAuth) +func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domainName, keyAuth) zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { @@ -132,6 +143,13 @@ func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error { return fmt.Errorf("netcup: %v", err) } + defer func() { + err = d.client.Logout(sessionID) + if err != nil { + log.Print("netcup: %v", err) + } + }() + hostname := strings.Replace(fqdn, "."+zone, "", 1) zone = acme.UnFqdn(zone) @@ -141,27 +159,20 @@ func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error { return fmt.Errorf("netcup: %v", err) } - record := CreateTxtRecord(hostname, value, 0) + record := createTxtRecord(hostname, value, 0) - idx, err := GetDNSRecordIdx(records, record) + idx, err := getDNSRecordIdx(records, record) if err != nil { return fmt.Errorf("netcup: %v", err) } records[idx].DeleteRecord = true - err = d.client.UpdateDNSRecord(sessionID, zone, records[idx]) + err = d.client.UpdateDNSRecord(sessionID, zone, []DNSRecord{records[idx]}) if err != nil { - if errLogout := d.client.Logout(sessionID); errLogout != nil { - return fmt.Errorf("netcup: %v; %v", err, errLogout) - } return fmt.Errorf("netcup: %v", err) } - err = d.client.Logout(sessionID) - if err != nil { - return fmt.Errorf("netcup: %v", err) - } return nil } @@ -170,3 +181,29 @@ func (d *DNSProvider) CleanUp(domainname, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } + +// getDNSRecordIdx searches a given array of DNSRecords for a given DNSRecord +// equivalence is determined by Destination and RecortType attributes +// returns index of given DNSRecord in given array of DNSRecords +func getDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) { + for index, element := range records { + if record.Destination == element.Destination && record.RecordType == element.RecordType { + return index, nil + } + } + return -1, fmt.Errorf("no DNS Record found") +} + +// createTxtRecord uses the supplied values to return a DNSRecord of type TXT for the dns-01 challenge +func createTxtRecord(hostname, value string, ttl int) DNSRecord { + return DNSRecord{ + ID: 0, + Hostname: hostname, + RecordType: "TXT", + Priority: "", + Destination: value, + DeleteRecord: false, + State: "", + TTL: ttl, + } +} diff --git a/vendor/golang.org/x/crypto/pkcs12/bmp-string.go b/vendor/golang.org/x/crypto/pkcs12/bmp-string.go new file mode 100644 index 000000000..233b8b62c --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/bmp-string.go @@ -0,0 +1,50 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "errors" + "unicode/utf16" +) + +// bmpString returns s encoded in UCS-2 with a zero terminator. +func bmpString(s string) ([]byte, error) { + // References: + // https://tools.ietf.org/html/rfc7292#appendix-B.1 + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane + // - non-BMP characters are encoded in UTF 16 by using a surrogate pair of 16-bit codes + // EncodeRune returns 0xfffd if the rune does not need special encoding + // - the above RFC provides the info that BMPStrings are NULL terminated. + + ret := make([]byte, 0, 2*len(s)+2) + + for _, r := range s { + if t, _ := utf16.EncodeRune(r); t != 0xfffd { + return nil, errors.New("pkcs12: string contains characters that cannot be encoded in UCS-2") + } + ret = append(ret, byte(r/256), byte(r%256)) + } + + return append(ret, 0, 0), nil +} + +func decodeBMPString(bmpString []byte) (string, error) { + if len(bmpString)%2 != 0 { + return "", errors.New("pkcs12: odd-length BMP string") + } + + // strip terminator if present + if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 { + bmpString = bmpString[:l-2] + } + + s := make([]uint16, 0, len(bmpString)/2) + for len(bmpString) > 0 { + s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1])) + bmpString = bmpString[2:] + } + + return string(utf16.Decode(s)), nil +} diff --git a/vendor/golang.org/x/crypto/pkcs12/crypto.go b/vendor/golang.org/x/crypto/pkcs12/crypto.go new file mode 100644 index 000000000..484ca51b7 --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/crypto.go @@ -0,0 +1,131 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "bytes" + "crypto/cipher" + "crypto/des" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + + "golang.org/x/crypto/pkcs12/internal/rc2" +) + +var ( + oidPBEWithSHAAnd3KeyTripleDESCBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3}) + oidPBEWithSHAAnd40BitRC2CBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 6}) +) + +// pbeCipher is an abstraction of a PKCS#12 cipher. +type pbeCipher interface { + // create returns a cipher.Block given a key. + create(key []byte) (cipher.Block, error) + // deriveKey returns a key derived from the given password and salt. + deriveKey(salt, password []byte, iterations int) []byte + // deriveKey returns an IV derived from the given password and salt. + deriveIV(salt, password []byte, iterations int) []byte +} + +type shaWithTripleDESCBC struct{} + +func (shaWithTripleDESCBC) create(key []byte) (cipher.Block, error) { + return des.NewTripleDESCipher(key) +} + +func (shaWithTripleDESCBC) deriveKey(salt, password []byte, iterations int) []byte { + return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 24) +} + +func (shaWithTripleDESCBC) deriveIV(salt, password []byte, iterations int) []byte { + return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8) +} + +type shaWith40BitRC2CBC struct{} + +func (shaWith40BitRC2CBC) create(key []byte) (cipher.Block, error) { + return rc2.New(key, len(key)*8) +} + +func (shaWith40BitRC2CBC) deriveKey(salt, password []byte, iterations int) []byte { + return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 5) +} + +func (shaWith40BitRC2CBC) deriveIV(salt, password []byte, iterations int) []byte { + return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8) +} + +type pbeParams struct { + Salt []byte + Iterations int +} + +func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { + var cipherType pbeCipher + + switch { + case algorithm.Algorithm.Equal(oidPBEWithSHAAnd3KeyTripleDESCBC): + cipherType = shaWithTripleDESCBC{} + case algorithm.Algorithm.Equal(oidPBEWithSHAAnd40BitRC2CBC): + cipherType = shaWith40BitRC2CBC{} + default: + return nil, 0, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported") + } + + var params pbeParams + if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil { + return nil, 0, err + } + + key := cipherType.deriveKey(params.Salt, password, params.Iterations) + iv := cipherType.deriveIV(params.Salt, password, params.Iterations) + + block, err := cipherType.create(key) + if err != nil { + return nil, 0, err + } + + return cipher.NewCBCDecrypter(block, iv), block.BlockSize(), nil +} + +func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error) { + cbc, blockSize, err := pbDecrypterFor(info.Algorithm(), password) + if err != nil { + return nil, err + } + + encrypted := info.Data() + if len(encrypted) == 0 { + return nil, errors.New("pkcs12: empty encrypted data") + } + if len(encrypted)%blockSize != 0 { + return nil, errors.New("pkcs12: input is not a multiple of the block size") + } + decrypted = make([]byte, len(encrypted)) + cbc.CryptBlocks(decrypted, encrypted) + + psLen := int(decrypted[len(decrypted)-1]) + if psLen == 0 || psLen > blockSize { + return nil, ErrDecryption + } + + if len(decrypted) < psLen { + return nil, ErrDecryption + } + ps := decrypted[len(decrypted)-psLen:] + decrypted = decrypted[:len(decrypted)-psLen] + if bytes.Compare(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) != 0 { + return nil, ErrDecryption + } + + return +} + +// decryptable abstracts an object that contains ciphertext. +type decryptable interface { + Algorithm() pkix.AlgorithmIdentifier + Data() []byte +} diff --git a/vendor/golang.org/x/crypto/pkcs12/errors.go b/vendor/golang.org/x/crypto/pkcs12/errors.go new file mode 100644 index 000000000..7377ce6fb --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/errors.go @@ -0,0 +1,23 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import "errors" + +var ( + // ErrDecryption represents a failure to decrypt the input. + ErrDecryption = errors.New("pkcs12: decryption error, incorrect padding") + + // ErrIncorrectPassword is returned when an incorrect password is detected. + // Usually, P12/PFX data is signed to be able to verify the password. + ErrIncorrectPassword = errors.New("pkcs12: decryption password incorrect") +) + +// NotImplementedError indicates that the input is not currently supported. +type NotImplementedError string + +func (e NotImplementedError) Error() string { + return "pkcs12: " + string(e) +} diff --git a/vendor/golang.org/x/crypto/pkcs12/internal/rc2/rc2.go b/vendor/golang.org/x/crypto/pkcs12/internal/rc2/rc2.go new file mode 100644 index 000000000..7499e3fb6 --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/internal/rc2/rc2.go @@ -0,0 +1,271 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rc2 implements the RC2 cipher +/* +https://www.ietf.org/rfc/rfc2268.txt +http://people.csail.mit.edu/rivest/pubs/KRRR98.pdf + +This code is licensed under the MIT license. +*/ +package rc2 + +import ( + "crypto/cipher" + "encoding/binary" +) + +// The rc2 block size in bytes +const BlockSize = 8 + +type rc2Cipher struct { + k [64]uint16 +} + +// New returns a new rc2 cipher with the given key and effective key length t1 +func New(key []byte, t1 int) (cipher.Block, error) { + // TODO(dgryski): error checking for key length + return &rc2Cipher{ + k: expandKey(key, t1), + }, nil +} + +func (*rc2Cipher) BlockSize() int { return BlockSize } + +var piTable = [256]byte{ + 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, + 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, + 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, + 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, + 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, + 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, + 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, + 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, + 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, + 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, + 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, + 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, + 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, + 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, + 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, + 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad, +} + +func expandKey(key []byte, t1 int) [64]uint16 { + + l := make([]byte, 128) + copy(l, key) + + var t = len(key) + var t8 = (t1 + 7) / 8 + var tm = byte(255 % uint(1<<(8+uint(t1)-8*uint(t8)))) + + for i := len(key); i < 128; i++ { + l[i] = piTable[l[i-1]+l[uint8(i-t)]] + } + + l[128-t8] = piTable[l[128-t8]&tm] + + for i := 127 - t8; i >= 0; i-- { + l[i] = piTable[l[i+1]^l[i+t8]] + } + + var k [64]uint16 + + for i := range k { + k[i] = uint16(l[2*i]) + uint16(l[2*i+1])*256 + } + + return k +} + +func rotl16(x uint16, b uint) uint16 { + return (x >> (16 - b)) | (x << b) +} + +func (c *rc2Cipher) Encrypt(dst, src []byte) { + + r0 := binary.LittleEndian.Uint16(src[0:]) + r1 := binary.LittleEndian.Uint16(src[2:]) + r2 := binary.LittleEndian.Uint16(src[4:]) + r3 := binary.LittleEndian.Uint16(src[6:]) + + var j int + + for j <= 16 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + + } + + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + for j <= 40 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + + } + + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + for j <= 60 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + } + + binary.LittleEndian.PutUint16(dst[0:], r0) + binary.LittleEndian.PutUint16(dst[2:], r1) + binary.LittleEndian.PutUint16(dst[4:], r2) + binary.LittleEndian.PutUint16(dst[6:], r3) +} + +func (c *rc2Cipher) Decrypt(dst, src []byte) { + + r0 := binary.LittleEndian.Uint16(src[0:]) + r1 := binary.LittleEndian.Uint16(src[2:]) + r2 := binary.LittleEndian.Uint16(src[4:]) + r3 := binary.LittleEndian.Uint16(src[6:]) + + j := 63 + + for j >= 44 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + } + + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + for j >= 20 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + + } + + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + for j >= 0 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + + } + + binary.LittleEndian.PutUint16(dst[0:], r0) + binary.LittleEndian.PutUint16(dst[2:], r1) + binary.LittleEndian.PutUint16(dst[4:], r2) + binary.LittleEndian.PutUint16(dst[6:], r3) +} diff --git a/vendor/golang.org/x/crypto/pkcs12/mac.go b/vendor/golang.org/x/crypto/pkcs12/mac.go new file mode 100644 index 000000000..5f38aa7de --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/mac.go @@ -0,0 +1,45 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/x509/pkix" + "encoding/asn1" +) + +type macData struct { + Mac digestInfo + MacSalt []byte + Iterations int `asn1:"optional,default:1"` +} + +// from PKCS#7: +type digestInfo struct { + Algorithm pkix.AlgorithmIdentifier + Digest []byte +} + +var ( + oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}) +) + +func verifyMac(macData *macData, message, password []byte) error { + if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) { + return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String()) + } + + key := pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20) + + mac := hmac.New(sha1.New, key) + mac.Write(message) + expectedMAC := mac.Sum(nil) + + if !hmac.Equal(macData.Mac.Digest, expectedMAC) { + return ErrIncorrectPassword + } + return nil +} diff --git a/vendor/golang.org/x/crypto/pkcs12/pbkdf.go b/vendor/golang.org/x/crypto/pkcs12/pbkdf.go new file mode 100644 index 000000000..5c419d41e --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/pbkdf.go @@ -0,0 +1,170 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "bytes" + "crypto/sha1" + "math/big" +) + +var ( + one = big.NewInt(1) +) + +// sha1Sum returns the SHA-1 hash of in. +func sha1Sum(in []byte) []byte { + sum := sha1.Sum(in) + return sum[:] +} + +// fillWithRepeats returns v*ceiling(len(pattern) / v) bytes consisting of +// repeats of pattern. +func fillWithRepeats(pattern []byte, v int) []byte { + if len(pattern) == 0 { + return nil + } + outputLen := v * ((len(pattern) + v - 1) / v) + return bytes.Repeat(pattern, (outputLen+len(pattern)-1)/len(pattern))[:outputLen] +} + +func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID byte, size int) (key []byte) { + // implementation of https://tools.ietf.org/html/rfc7292#appendix-B.2 , RFC text verbatim in comments + + // Let H be a hash function built around a compression function f: + + // Z_2^u x Z_2^v -> Z_2^u + + // (that is, H has a chaining variable and output of length u bits, and + // the message input to the compression function of H is v bits). The + // values for u and v are as follows: + + // HASH FUNCTION VALUE u VALUE v + // MD2, MD5 128 512 + // SHA-1 160 512 + // SHA-224 224 512 + // SHA-256 256 512 + // SHA-384 384 1024 + // SHA-512 512 1024 + // SHA-512/224 224 1024 + // SHA-512/256 256 1024 + + // Furthermore, let r be the iteration count. + + // We assume here that u and v are both multiples of 8, as are the + // lengths of the password and salt strings (which we denote by p and s, + // respectively) and the number n of pseudorandom bits required. In + // addition, u and v are of course non-zero. + + // For information on security considerations for MD5 [19], see [25] and + // [1], and on those for MD2, see [18]. + + // The following procedure can be used to produce pseudorandom bits for + // a particular "purpose" that is identified by a byte called "ID". + // This standard specifies 3 different values for the ID byte: + + // 1. If ID=1, then the pseudorandom bits being produced are to be used + // as key material for performing encryption or decryption. + + // 2. If ID=2, then the pseudorandom bits being produced are to be used + // as an IV (Initial Value) for encryption or decryption. + + // 3. If ID=3, then the pseudorandom bits being produced are to be used + // as an integrity key for MACing. + + // 1. Construct a string, D (the "diversifier"), by concatenating v/8 + // copies of ID. + var D []byte + for i := 0; i < v; i++ { + D = append(D, ID) + } + + // 2. Concatenate copies of the salt together to create a string S of + // length v(ceiling(s/v)) bits (the final copy of the salt may be + // truncated to create S). Note that if the salt is the empty + // string, then so is S. + + S := fillWithRepeats(salt, v) + + // 3. Concatenate copies of the password together to create a string P + // of length v(ceiling(p/v)) bits (the final copy of the password + // may be truncated to create P). Note that if the password is the + // empty string, then so is P. + + P := fillWithRepeats(password, v) + + // 4. Set I=S||P to be the concatenation of S and P. + I := append(S, P...) + + // 5. Set c=ceiling(n/u). + c := (size + u - 1) / u + + // 6. For i=1, 2, ..., c, do the following: + A := make([]byte, c*20) + var IjBuf []byte + for i := 0; i < c; i++ { + // A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1, + // H(H(H(... H(D||I)))) + Ai := hash(append(D, I...)) + for j := 1; j < r; j++ { + Ai = hash(Ai) + } + copy(A[i*20:], Ai[:]) + + if i < c-1 { // skip on last iteration + // B. Concatenate copies of Ai to create a string B of length v + // bits (the final copy of Ai may be truncated to create B). + var B []byte + for len(B) < v { + B = append(B, Ai[:]...) + } + B = B[:v] + + // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit + // blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by + // setting I_j=(I_j+B+1) mod 2^v for each j. + { + Bbi := new(big.Int).SetBytes(B) + Ij := new(big.Int) + + for j := 0; j < len(I)/v; j++ { + Ij.SetBytes(I[j*v : (j+1)*v]) + Ij.Add(Ij, Bbi) + Ij.Add(Ij, one) + Ijb := Ij.Bytes() + // We expect Ijb to be exactly v bytes, + // if it is longer or shorter we must + // adjust it accordingly. + if len(Ijb) > v { + Ijb = Ijb[len(Ijb)-v:] + } + if len(Ijb) < v { + if IjBuf == nil { + IjBuf = make([]byte, v) + } + bytesShort := v - len(Ijb) + for i := 0; i < bytesShort; i++ { + IjBuf[i] = 0 + } + copy(IjBuf[bytesShort:], Ijb) + Ijb = IjBuf + } + copy(I[j*v:(j+1)*v], Ijb) + } + } + } + } + // 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom + // bit string, A. + + // 8. Use the first n bits of A as the output of this entire process. + return A[:size] + + // If the above process is being used to generate a DES key, the process + // should be used to create 64 random bits, and the key's parity bits + // should be set after the 64 bits have been produced. Similar concerns + // hold for 2-key and 3-key triple-DES keys, for CDMF keys, and for any + // similar keys with parity bits "built into them". +} diff --git a/vendor/golang.org/x/crypto/pkcs12/pkcs12.go b/vendor/golang.org/x/crypto/pkcs12/pkcs12.go new file mode 100644 index 000000000..eff9ad3a9 --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/pkcs12.go @@ -0,0 +1,346 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package pkcs12 implements some of PKCS#12. +// +// This implementation is distilled from https://tools.ietf.org/html/rfc7292 +// and referenced documents. It is intended for decoding P12/PFX-stored +// certificates and keys for use with the crypto/tls package. +package pkcs12 + +import ( + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/hex" + "encoding/pem" + "errors" +) + +var ( + oidDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 1}) + oidEncryptedDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 6}) + + oidFriendlyName = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 20}) + oidLocalKeyID = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 21}) + oidMicrosoftCSPName = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 311, 17, 1}) +) + +type pfxPdu struct { + Version int + AuthSafe contentInfo + MacData macData `asn1:"optional"` +} + +type contentInfo struct { + ContentType asn1.ObjectIdentifier + Content asn1.RawValue `asn1:"tag:0,explicit,optional"` +} + +type encryptedData struct { + Version int + EncryptedContentInfo encryptedContentInfo +} + +type encryptedContentInfo struct { + ContentType asn1.ObjectIdentifier + ContentEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedContent []byte `asn1:"tag:0,optional"` +} + +func (i encryptedContentInfo) Algorithm() pkix.AlgorithmIdentifier { + return i.ContentEncryptionAlgorithm +} + +func (i encryptedContentInfo) Data() []byte { return i.EncryptedContent } + +type safeBag struct { + Id asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"tag:0,explicit"` + Attributes []pkcs12Attribute `asn1:"set,optional"` +} + +type pkcs12Attribute struct { + Id asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"set"` +} + +type encryptedPrivateKeyInfo struct { + AlgorithmIdentifier pkix.AlgorithmIdentifier + EncryptedData []byte +} + +func (i encryptedPrivateKeyInfo) Algorithm() pkix.AlgorithmIdentifier { + return i.AlgorithmIdentifier +} + +func (i encryptedPrivateKeyInfo) Data() []byte { + return i.EncryptedData +} + +// PEM block types +const ( + certificateType = "CERTIFICATE" + privateKeyType = "PRIVATE KEY" +) + +// unmarshal calls asn1.Unmarshal, but also returns an error if there is any +// trailing data after unmarshaling. +func unmarshal(in []byte, out interface{}) error { + trailing, err := asn1.Unmarshal(in, out) + if err != nil { + return err + } + if len(trailing) != 0 { + return errors.New("pkcs12: trailing data found") + } + return nil +} + +// ConvertToPEM converts all "safe bags" contained in pfxData to PEM blocks. +func ToPEM(pfxData []byte, password string) ([]*pem.Block, error) { + encodedPassword, err := bmpString(password) + if err != nil { + return nil, ErrIncorrectPassword + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword) + + if err != nil { + return nil, err + } + + blocks := make([]*pem.Block, 0, len(bags)) + for _, bag := range bags { + block, err := convertBag(&bag, encodedPassword) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + } + + return blocks, nil +} + +func convertBag(bag *safeBag, password []byte) (*pem.Block, error) { + block := &pem.Block{ + Headers: make(map[string]string), + } + + for _, attribute := range bag.Attributes { + k, v, err := convertAttribute(&attribute) + if err != nil { + return nil, err + } + block.Headers[k] = v + } + + switch { + case bag.Id.Equal(oidCertBag): + block.Type = certificateType + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, err + } + block.Bytes = certsData + case bag.Id.Equal(oidPKCS8ShroundedKeyBag): + block.Type = privateKeyType + + key, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, password) + if err != nil { + return nil, err + } + + switch key := key.(type) { + case *rsa.PrivateKey: + block.Bytes = x509.MarshalPKCS1PrivateKey(key) + case *ecdsa.PrivateKey: + block.Bytes, err = x509.MarshalECPrivateKey(key) + if err != nil { + return nil, err + } + default: + return nil, errors.New("found unknown private key type in PKCS#8 wrapping") + } + default: + return nil, errors.New("don't know how to convert a safe bag of type " + bag.Id.String()) + } + return block, nil +} + +func convertAttribute(attribute *pkcs12Attribute) (key, value string, err error) { + isString := false + + switch { + case attribute.Id.Equal(oidFriendlyName): + key = "friendlyName" + isString = true + case attribute.Id.Equal(oidLocalKeyID): + key = "localKeyId" + case attribute.Id.Equal(oidMicrosoftCSPName): + // This key is chosen to match OpenSSL. + key = "Microsoft CSP Name" + isString = true + default: + return "", "", errors.New("pkcs12: unknown attribute with OID " + attribute.Id.String()) + } + + if isString { + if err := unmarshal(attribute.Value.Bytes, &attribute.Value); err != nil { + return "", "", err + } + if value, err = decodeBMPString(attribute.Value.Bytes); err != nil { + return "", "", err + } + } else { + var id []byte + if err := unmarshal(attribute.Value.Bytes, &id); err != nil { + return "", "", err + } + value = hex.EncodeToString(id) + } + + return key, value, nil +} + +// Decode extracts a certificate and private key from pfxData. This function +// assumes that there is only one certificate and only one private key in the +// pfxData. +func Decode(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, err error) { + encodedPassword, err := bmpString(password) + if err != nil { + return nil, nil, err + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword) + if err != nil { + return nil, nil, err + } + + if len(bags) != 2 { + err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU") + return + } + + for _, bag := range bags { + switch { + case bag.Id.Equal(oidCertBag): + if certificate != nil { + err = errors.New("pkcs12: expected exactly one certificate bag") + } + + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, nil, err + } + certs, err := x509.ParseCertificates(certsData) + if err != nil { + return nil, nil, err + } + if len(certs) != 1 { + err = errors.New("pkcs12: expected exactly one certificate in the certBag") + return nil, nil, err + } + certificate = certs[0] + + case bag.Id.Equal(oidPKCS8ShroundedKeyBag): + if privateKey != nil { + err = errors.New("pkcs12: expected exactly one key bag") + } + + if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword); err != nil { + return nil, nil, err + } + } + } + + if certificate == nil { + return nil, nil, errors.New("pkcs12: certificate missing") + } + if privateKey == nil { + return nil, nil, errors.New("pkcs12: private key missing") + } + + return +} + +func getSafeContents(p12Data, password []byte) (bags []safeBag, updatedPassword []byte, err error) { + pfx := new(pfxPdu) + if err := unmarshal(p12Data, pfx); err != nil { + return nil, nil, errors.New("pkcs12: error reading P12 data: " + err.Error()) + } + + if pfx.Version != 3 { + return nil, nil, NotImplementedError("can only decode v3 PFX PDU's") + } + + if !pfx.AuthSafe.ContentType.Equal(oidDataContentType) { + return nil, nil, NotImplementedError("only password-protected PFX is implemented") + } + + // unmarshal the explicit bytes in the content for type 'data' + if err := unmarshal(pfx.AuthSafe.Content.Bytes, &pfx.AuthSafe.Content); err != nil { + return nil, nil, err + } + + if len(pfx.MacData.Mac.Algorithm.Algorithm) == 0 { + return nil, nil, errors.New("pkcs12: no MAC in data") + } + + if err := verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password); err != nil { + if err == ErrIncorrectPassword && len(password) == 2 && password[0] == 0 && password[1] == 0 { + // some implementations use an empty byte array + // for the empty string password try one more + // time with empty-empty password + password = nil + err = verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password) + } + if err != nil { + return nil, nil, err + } + } + + var authenticatedSafe []contentInfo + if err := unmarshal(pfx.AuthSafe.Content.Bytes, &authenticatedSafe); err != nil { + return nil, nil, err + } + + if len(authenticatedSafe) != 2 { + return nil, nil, NotImplementedError("expected exactly two items in the authenticated safe") + } + + for _, ci := range authenticatedSafe { + var data []byte + + switch { + case ci.ContentType.Equal(oidDataContentType): + if err := unmarshal(ci.Content.Bytes, &data); err != nil { + return nil, nil, err + } + case ci.ContentType.Equal(oidEncryptedDataContentType): + var encryptedData encryptedData + if err := unmarshal(ci.Content.Bytes, &encryptedData); err != nil { + return nil, nil, err + } + if encryptedData.Version != 0 { + return nil, nil, NotImplementedError("only version 0 of EncryptedData is supported") + } + if data, err = pbDecrypt(encryptedData.EncryptedContentInfo, password); err != nil { + return nil, nil, err + } + default: + return nil, nil, NotImplementedError("only data and encryptedData content types are supported in authenticated safe") + } + + var safeContents []safeBag + if err := unmarshal(data, &safeContents); err != nil { + return nil, nil, err + } + bags = append(bags, safeContents...) + } + + return bags, password, nil +} diff --git a/vendor/golang.org/x/crypto/pkcs12/safebags.go b/vendor/golang.org/x/crypto/pkcs12/safebags.go new file mode 100644 index 000000000..def1f7b98 --- /dev/null +++ b/vendor/golang.org/x/crypto/pkcs12/safebags.go @@ -0,0 +1,57 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "crypto/x509" + "encoding/asn1" + "errors" +) + +var ( + // see https://tools.ietf.org/html/rfc7292#appendix-D + oidCertTypeX509Certificate = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 22, 1}) + oidPKCS8ShroundedKeyBag = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 10, 1, 2}) + oidCertBag = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 10, 1, 3}) +) + +type certBag struct { + Id asn1.ObjectIdentifier + Data []byte `asn1:"tag:0,explicit"` +} + +func decodePkcs8ShroudedKeyBag(asn1Data, password []byte) (privateKey interface{}, err error) { + pkinfo := new(encryptedPrivateKeyInfo) + if err = unmarshal(asn1Data, pkinfo); err != nil { + return nil, errors.New("pkcs12: error decoding PKCS#8 shrouded key bag: " + err.Error()) + } + + pkData, err := pbDecrypt(pkinfo, password) + if err != nil { + return nil, errors.New("pkcs12: error decrypting PKCS#8 shrouded key bag: " + err.Error()) + } + + ret := new(asn1.RawValue) + if err = unmarshal(pkData, ret); err != nil { + return nil, errors.New("pkcs12: error unmarshaling decrypted private key: " + err.Error()) + } + + if privateKey, err = x509.ParsePKCS8PrivateKey(pkData); err != nil { + return nil, errors.New("pkcs12: error parsing PKCS#8 private key: " + err.Error()) + } + + return privateKey, nil +} + +func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) { + bag := new(certBag) + if err := unmarshal(asn1Data, bag); err != nil { + return nil, errors.New("pkcs12: error decoding cert bag: " + err.Error()) + } + if !bag.Id.Equal(oidCertTypeX509Certificate) { + return nil, NotImplementedError("only X509 certificates are supported") + } + return bag.Data, nil +}