diff --git a/docs/resources/group.md b/docs/resources/group.md index c818e1614a..c31c380a7e 100644 --- a/docs/resources/group.md +++ b/docs/resources/group.md @@ -18,6 +18,8 @@ If specifying owners for a group, which are user principals, this resource addit When authenticated with a user principal, this resource requires one of the following directory roles: `Groups Administrator`, `User Administrator` or `Global Administrator` +When creating this resource in administrative units exclusively, the role `Groups Administrator` is required to be scoped on any administrative unit used. + The `external_senders_allowed`, `auto_subscribe_new_members`, `hide_from_address_lists` and `hide_from_outlook_clients` properties can only be configured when authenticating as a user and cannot be configured when authenticating as a service principal. Additionally, the user being used for authentication must be a Member of the tenant where the group is being managed and _not_ a Guest. This is a known API issue; please see the [Microsoft Graph Known Issues](https://docs.microsoft.com/en-us/graph/known-issues#groups) official documentation. ## Example Usage @@ -106,6 +108,10 @@ resource "azuread_group" "example" { The following arguments are supported: +* `administrative_unit_ids` - (Optional) The object IDs of administrative units in which the group is a member. If specified, new groups will be created in the scope of the first administrative unit and added to the others. If empty, new groups will be created at the tenant level. + +!> **Warning** Do not use the `administrative_unit_ids` property at the same time as the [azuread_administrative_unit_member](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/administrative_unit_member) resource, or the `members` property of the [azuread_administrative_unit](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/administrative_unit#members) resource, _for the same group_. Doing so will cause a conflict and administrative unit members will be removed. + * `assignable_to_role` - (Optional) Indicates whether this group can be assigned to an Azure Active Directory role. Can only be `true` for security-enabled groups. Changing this forces a new resource to be created. * `auto_subscribe_new_members` - (Optional) Indicates whether new members added to the group will be auto-subscribed to receive email notifications. Can only be set for Unified groups. diff --git a/go.mod b/go.mod index afccc91299..5ef9ab0442 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0 - github.com/manicminer/hamilton v0.54.0 + github.com/manicminer/hamilton v0.57.0 golang.org/x/text v0.3.7 ) @@ -54,6 +54,7 @@ require ( google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect + software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect ) go 1.19 diff --git a/go.sum b/go.sum index cfeb12609b..39484db29c 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/manicminer/hamilton v0.54.0 h1:vnjgE7Eer9dnrAd78qZHPK6CTNCwwFmuVtvBjjl4OtY= -github.com/manicminer/hamilton v0.54.0/go.mod h1:lbVyngC+/nCWuDp8UhC6Bw+bh7jcP/E+YwqzHTmzemk= +github.com/manicminer/hamilton v0.57.0 h1:H+p4l7gfI4Fc6t0NGrJE0g6vAcPSw8gyjT1nQeSZrfQ= +github.com/manicminer/hamilton v0.57.0/go.mod h1:fFR5k3IJ/QCsEgT9TsD9K2l2dv2tmk7Qpeze1hsUJH4= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -303,7 +303,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 h1:O8uGbHCqlTp2P6QJSLmCojM4mN6UemYv8K+dCnmHmu0= golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -602,3 +602,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/internal/services/groups/client/client.go b/internal/services/groups/client/client.go index bf35e86efe..fb2f665fef 100644 --- a/internal/services/groups/client/client.go +++ b/internal/services/groups/client/client.go @@ -7,11 +7,18 @@ import ( ) type Client struct { - DirectoryObjectsClient *msgraph.DirectoryObjectsClient - GroupsClient *msgraph.GroupsClient + AdministrativeUnitsClient *msgraph.AdministrativeUnitsClient + DirectoryObjectsClient *msgraph.DirectoryObjectsClient + GroupsClient *msgraph.GroupsClient } func NewClient(o *common.ClientOptions) *Client { + administrativeUnitsClient := msgraph.NewAdministrativeUnitsClient(o.TenantID) + o.ConfigureClient(&administrativeUnitsClient.BaseClient) + + // SDK uses wrong endpoint for v1.0 API, see https://github.com/manicminer/hamilton/issues/222 + administrativeUnitsClient.BaseClient.ApiVersion = msgraph.VersionBeta + directoryObjectsClient := msgraph.NewDirectoryObjectsClient(o.TenantID) o.ConfigureClient(&directoryObjectsClient.BaseClient) @@ -22,7 +29,8 @@ func NewClient(o *common.ClientOptions) *Client { groupsClient.BaseClient.ApiVersion = msgraph.VersionBeta return &Client{ - DirectoryObjectsClient: directoryObjectsClient, - GroupsClient: groupsClient, + AdministrativeUnitsClient: administrativeUnitsClient, + DirectoryObjectsClient: directoryObjectsClient, + GroupsClient: groupsClient, } } diff --git a/internal/services/groups/group_resource.go b/internal/services/groups/group_resource.go index 7cdd86dbc7..0b448cfd4e 100644 --- a/internal/services/groups/group_resource.go +++ b/internal/services/groups/group_resource.go @@ -56,6 +56,16 @@ func groupResource() *schema.Resource { ValidateDiagFunc: validate.NoEmptyStrings, }, + "administrative_unit_ids": { + Description: "The administrative unit IDs in which the group should be. If empty, the group will be created at the tenant level.", + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsUUID, + }, + }, + "assignable_to_role": { Description: "Indicates whether this group can be assigned to an Azure Active Directory role. This property can only be `true` for security-enabled groups.", Type: schema.TypeBool, @@ -406,6 +416,7 @@ func groupResourceCustomizeDiff(ctx context.Context, diff *schema.ResourceDiff, func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*clients.Client).Groups.GroupsClient directoryObjectsClient := meta.(*clients.Client).Groups.DirectoryObjectsClient + administrativeUnitsClient := meta.(*clients.Client).Groups.AdministrativeUnitsClient callerId := meta.(*clients.Client).ObjectID displayName := d.Get("display_name").(string) @@ -451,7 +462,12 @@ func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta inter description := d.Get("description").(string) + odataType := odata.TypeGroup + properties := msgraph.Group{ + DirectoryObject: msgraph.DirectoryObject{ + ODataType: &odataType, + }, Description: utils.NullableString(description), DisplayName: utils.String(displayName), GroupTypes: &groupTypes, @@ -566,9 +582,30 @@ func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta inter // Set the initial owners, which either be the calling principal, or up to 20 of the owners specified in configuration properties.Owners = &ownersFirst20 - group, _, err := client.Create(ctx, properties) - if err != nil { - return tf.ErrorDiagF(err, "Creating group %q", displayName) + var group *msgraph.Group + var err error + + if v, ok := d.GetOk("administrative_unit_ids"); ok { + administrativeUnitIds := tf.ExpandStringSlice(v.(*schema.Set).List()) + for i, administrativeUnitId := range administrativeUnitIds { + // Create the group in the first administrative unit, as this requires fewer permissions than creating it at tenant level + if i == 0 { + group, _, err = administrativeUnitsClient.CreateGroup(ctx, administrativeUnitId, &properties) + if err != nil { + return tf.ErrorDiagF(err, "Creating group in administrative unit with ID %q, %q", administrativeUnitId, displayName) + } + } else { + err := addGroupToAdministrativeUnit(ctx, administrativeUnitsClient, administrativeUnitId, group) + if err != nil { + return tf.ErrorDiagF(err, "Could not add group %q to administrative unit with object ID: %q", *group.ID(), administrativeUnitId) + } + } + } + } else { + group, _, err = client.Create(ctx, properties) + if err != nil { + return tf.ErrorDiagF(err, "Creating group %q", displayName) + } } if group.ID() == nil { @@ -795,12 +832,21 @@ func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta inter } } + // We have observed that when creating a group with an administrative_unit_id and querying the group with the /groups endpoint and specifying $select=allowExternalSenders,autoSubscribeNewMembers,hideFromAddressLists,hideFromOutlookClients, it returns a 404 for ~11 minutes. + if _, ok := d.GetOk("administrative_unit_ids"); ok { + meta.(*clients.Client).Groups.GroupsClient.BaseClient.DisableRetries = false + meta.(*clients.Client).Groups.GroupsClient.BaseClient.RetryableClient.RetryWaitMax = 1 * time.Minute + meta.(*clients.Client).Groups.GroupsClient.BaseClient.RetryableClient.RetryWaitMin = 10 * time.Second + meta.(*clients.Client).Groups.GroupsClient.BaseClient.RetryableClient.RetryMax = 15 + } + return groupResourceRead(ctx, d, meta) } func groupResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*clients.Client).Groups.GroupsClient directoryObjectsClient := meta.(*clients.Client).Groups.DirectoryObjectsClient + administrativeUnitClient := meta.(*clients.Client).Groups.AdministrativeUnitsClient callerId := meta.(*clients.Client).ObjectID groupId := d.Id() @@ -1065,6 +1111,40 @@ func groupResourceUpdate(ctx context.Context, d *schema.ResourceData, meta inter } } + if v := d.Get("administrative_unit_ids"); d.HasChange("administrative_unit_ids") { + administrativeUnits, _, err := client.ListAdministrativeUnitMemberships(ctx, *group.ID()) + if err != nil { + return tf.ErrorDiagPathF(err, "administrative_units", "Could not retrieve administrative units for group with object ID %q", d.Id()) + } + + var existingAdministrativeUnits []string + for _, administrativeUnit := range *administrativeUnits { + existingAdministrativeUnits = append(existingAdministrativeUnits, *administrativeUnit.ID) + } + + desiredAdministrativeUnits := tf.ExpandStringSlice(v.(*schema.Set).List()) + administrativeUnitsToLeave := utils.Difference(existingAdministrativeUnits, desiredAdministrativeUnits) + administrativeUnitsToJoin := utils.Difference(desiredAdministrativeUnits, existingAdministrativeUnits) + + if len(administrativeUnitsToJoin) > 0 { + for _, newAdministrativeUnitId := range administrativeUnitsToJoin { + err := addGroupToAdministrativeUnit(ctx, administrativeUnitClient, newAdministrativeUnitId, &group) + if err != nil { + return tf.ErrorDiagF(err, "Could not add group %q to administrative unit with object ID: %q", *group.ID(), newAdministrativeUnitId) + } + } + } + + if len(administrativeUnitsToLeave) > 0 { + for _, oldAdministrativeUnitId := range administrativeUnitsToLeave { + memberIds := []string{d.Id()} + if _, err := administrativeUnitClient.RemoveMembers(ctx, oldAdministrativeUnitId, &memberIds); err != nil { + return tf.ErrorDiagF(err, "Could not remove group from administrative unit with object ID: %q", oldAdministrativeUnitId) + } + } + } + } + return groupResourceRead(ctx, d, meta) } @@ -1153,6 +1233,22 @@ func groupResourceRead(ctx context.Context, d *schema.ResourceData, meta interfa } tf.Set(d, "members", members) + administrativeUnits, _, err := client.ListAdministrativeUnitMemberships(ctx, *group.ID()) + if err != nil { + return tf.ErrorDiagPathF(err, "administrative_units", "Could not retrieve administrative units for group with object ID %q", d.Id()) + } + + auIdMembers := make([]string, 0) + for _, administrativeUnit := range *administrativeUnits { + auIdMembers = append(auIdMembers, *administrativeUnit.ID) + } + + if len(auIdMembers) > 0 { + tf.Set(d, "administrative_unit_ids", &auIdMembers) + } else { + tf.Set(d, "administrative_unit_ids", nil) + } + preventDuplicates := false if v := d.Get("prevent_duplicate_names").(bool); v { preventDuplicates = v @@ -1194,3 +1290,13 @@ func groupResourceDelete(ctx context.Context, d *schema.ResourceData, meta inter return nil } + +func addGroupToAdministrativeUnit(ctx context.Context, auClient *msgraph.AdministrativeUnitsClient, administrativeUnitId string, group *msgraph.Group) error { + members := msgraph.Members{ + group.DirectoryObject, + } + members[0].ODataId = (*odata.Id)(utils.String(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", + auClient.BaseClient.Endpoint, auClient.BaseClient.TenantId, *group.DirectoryObject.ID()))) + _, err := auClient.AddMembers(ctx, administrativeUnitId, &members) + return err +} diff --git a/internal/services/groups/group_resource_test.go b/internal/services/groups/group_resource_test.go index dcefc8f3a6..dc94b77d34 100644 --- a/internal/services/groups/group_resource_test.go +++ b/internal/services/groups/group_resource_test.go @@ -434,6 +434,38 @@ func TestAccGroup_visibility(t *testing.T) { }) } +func TestAccGroup_administrativeUnit(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_group", "test") + r := GroupResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.administrativeUnits(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("administrative_unit_ids.#").HasValue("2"), + ), + }, + data.ImportStep("administrative_unit_ids"), + { + Config: r.administrativeUnitsWithoutAssociation(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("administrative_unit_ids.#").HasValue("0"), + ), + }, + data.ImportStep("administrative_unit_ids"), + { + Config: r.administrativeUnits(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("administrative_unit_ids.#").HasValue("2"), + ), + }, + data.ImportStep("administrative_unit_ids"), + }) +} + func (r GroupResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { client := clients.Groups.GroupsClient client.BaseClient.DisableRetries = true @@ -925,3 +957,38 @@ resource "azuread_group" "test" { } `, data.RandomInteger) } + +func (r GroupResource) administrativeUnits(data acceptance.TestData) string { + return fmt.Sprintf(` +resource "azuread_administrative_unit" "test" { + display_name = "acctestGroup-administrative-unit-%[1]d" +} + +resource "azuread_administrative_unit" "test2" { + display_name = "acctestGroup-administrative-unit-%[1]d" +} + +resource "azuread_group" "test" { + display_name = "acctestGroup-%[1]d" + security_enabled = true + administrative_unit_ids = [azuread_administrative_unit.test.id, azuread_administrative_unit.test2.id] +} +`, data.RandomInteger, data.RandomInteger) +} + +func (r GroupResource) administrativeUnitsWithoutAssociation(data acceptance.TestData) string { + return fmt.Sprintf(` +resource "azuread_administrative_unit" "test" { + display_name = "acctestGroup-administrative-unit-%[1]d" +} + +resource "azuread_administrative_unit" "test2" { + display_name = "acctestGroup-administrative-unit-%[1]d" +} + +resource "azuread_group" "test" { + display_name = "acctestGroup-%[1]d" + security_enabled = true +} +`, data.RandomInteger, data.RandomInteger) +} diff --git a/vendor/github.com/manicminer/hamilton/auth/auth.go b/vendor/github.com/manicminer/hamilton/auth/auth.go index faf7d34549..6ce507287a 100644 --- a/vendor/github.com/manicminer/hamilton/auth/auth.go +++ b/vendor/github.com/manicminer/hamilton/auth/auth.go @@ -8,8 +8,8 @@ import ( "os" "strings" - "golang.org/x/crypto/pkcs12" "golang.org/x/oauth2" + pkcs12 "software.sslmate.com/src/go-pkcs12" "github.com/manicminer/hamilton/environments" ) @@ -132,7 +132,7 @@ func NewClientCertificateAuthorizer(ctx context.Context, environment environment } } - key, cert, err := pkcs12.Decode(pfxData, pfxPass) + key, cert, _, err := pkcs12.DecodeChain(pfxData, pfxPass) if err != nil { return nil, fmt.Errorf("could not decode pkcs12 credential store: %s", err) } diff --git a/vendor/github.com/manicminer/hamilton/environments/published.go b/vendor/github.com/manicminer/hamilton/environments/published.go index 23deeb47f3..dfc8c32c0c 100644 --- a/vendor/github.com/manicminer/hamilton/environments/published.go +++ b/vendor/github.com/manicminer/hamilton/environments/published.go @@ -5,6 +5,8 @@ type ApiAppId = string // PublishedApis is a map containing Application IDs for well known APIs published by Microsoft. // They can be used to acquire access tokens, but are primarily described here for easy inclusion in // application manifests and service principal assignments. +// These are collected from various sources, including the Azure Portal, CLI, PowerShell and the following doc: +// https://learn.microsoft.com/en-us/troubleshoot/azure/active-directory/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications var PublishedApis = map[string]ApiAppId{ "ApplicationInsights": "f5c26e74-f226-4ae8-85f0-b4af0080ac9e", "AttestationService": "c61423b7-1d1f-430d-b444-0eee53298103", @@ -79,8 +81,11 @@ var PublishedApis = map[string]ApiAppId{ "MicrosoftAzureFrontDoorCdn": "205478c0-bd83-4e1b-a9d6-db63a3e1e1c8", "Microsoft365DataAtRestEncryption": "c066d759-24ae-40e7-a56f-027002b5d3e4", "MicrosoftGraph": "00000003-0000-0000-c000-000000000000", - "Microsoft.StorageSync": "9469b9f5-6722-4481-a2b2-14ed560b706f", "MicrosoftInvoicing": "b6b84568-6c01-4981-a80f-09da9a20bbed", + "MicrosoftOffice": "d3590ed6-52b3-4102-aeff-aad2292ab01c", + "Microsoft.StorageSync": "9469b9f5-6722-4481-a2b2-14ed560b706f", + "MicrosoftTeams": "1fec8e78-bce4-4aaf-ab1b-5451cc387264", + "MicrosoftTeamsWebClient": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346", "Office365Connectors": "48af08dc-f6d2-435f-b2a7-069abd99c086", "Office365Demeter": "982bda36-4632-4165-a46a-9863b1bbcf7d", "Office365DwEngineV2": "441509e5-a165-4363-8ee7-bcf0b7d26739", @@ -90,25 +95,29 @@ var PublishedApis = map[string]ApiAppId{ "Office365Management": "c5393580-f805-4401-95e8-94b7a6ef2fc2", "Office365Portal": "00000006-0000-0ff1-ce00-000000000000", "Office365SharePointOnline": "00000003-0000-0ff1-ce00-000000000000", + "Office365SuiteUx": "4345a7b9-9a63-4910-a426-35363201d503", "Office365Zoom": "0d38933a-0bbd-41ca-9ebd-28c4b5ba7cb7", + "OfficeHome": "4765445b-32c6-49b0-83e6-1d93765276ca", + "OfficeUwpPwa": "0ec893e0-5785-4de6-99da-4ed124e5296c", "OneNote": "2d4d3d8e-2be3-4bef-9f87-7875a61c29de", "OneProfileService": "b2cc270f-563e-4d8a-af47-f00963a71dcd", "OssRdbms": "123cd850-d9df-40bd-94d5-c9f07b7fa203", - "PeopleCardsService": "394866fc-eedb-4f01-8536-3ff84b16be2a", - "PolicyAdministrationService": "0469d4cd-df37-4d93-8a61-f8c75b809164", - "PowerAppsRuntimeService": "82f77645-8a66-4745-bcdf-9706824f9ad0", - "PowerBiService": "00000009-0000-0000-c000-000000000000", - "Purview": "73c2949e-da2d-457a-9607-fcc665198967", - "RightsManagementServices": "00000012-0000-0000-c000-000000000000", - "ServiceTrust": "d6fdaa33-e821-4211-83d0-cf74736489e1", - "Signup": "b4bddae8-ab25-483e-8670-df09b9f1d0ea", - "SkypeForBusinessOnline": "00000004-0000-0ff1-ce00-000000000000", - "SpeechRecognition": "1a6fcee6-0816-469b-acac-fe7ef2e87b83", - "TargetedMessagingService": "4c4f550b-42b2-4a16-93f9-fdb9e01bb6ed", - "TeamsServices": "cc15fd57-2c6c-4117-a88c-83b1d56b4bbe", - "ThreatProtection": "8ee8fdad-f234-4243-8f3b-15c294843740", - "UniversalPrint": "da9b70f6-5323-4ce6-ae5c-88dcc5082966", - "WindowsDefenderAtp": "fc780465-2017-40d4-a0c5-307022471b92", - "WindowsVirtualDesktop": "9cdead84-a844-4324-93f2-b2e6bb768d07", - "Yammer": "00000005-0000-0ff1-ce00-000000000000", + "OssRdbmsPostgreSqlFlexibleServerAadAuthentication": "5657e26c-cc92-45d9-bc47-9da6cfdb4ed9", + "PeopleCardsService": "394866fc-eedb-4f01-8536-3ff84b16be2a", + "PolicyAdministrationService": "0469d4cd-df37-4d93-8a61-f8c75b809164", + "PowerAppsRuntimeService": "82f77645-8a66-4745-bcdf-9706824f9ad0", + "PowerBiService": "00000009-0000-0000-c000-000000000000", + "Purview": "73c2949e-da2d-457a-9607-fcc665198967", + "RightsManagementServices": "00000012-0000-0000-c000-000000000000", + "ServiceTrust": "d6fdaa33-e821-4211-83d0-cf74736489e1", + "Signup": "b4bddae8-ab25-483e-8670-df09b9f1d0ea", + "SkypeForBusinessOnline": "00000004-0000-0ff1-ce00-000000000000", + "SpeechRecognition": "1a6fcee6-0816-469b-acac-fe7ef2e87b83", + "TargetedMessagingService": "4c4f550b-42b2-4a16-93f9-fdb9e01bb6ed", + "TeamsServices": "cc15fd57-2c6c-4117-a88c-83b1d56b4bbe", + "ThreatProtection": "8ee8fdad-f234-4243-8f3b-15c294843740", + "UniversalPrint": "da9b70f6-5323-4ce6-ae5c-88dcc5082966", + "WindowsDefenderAtp": "fc780465-2017-40d4-a0c5-307022471b92", + "WindowsVirtualDesktop": "9cdead84-a844-4324-93f2-b2e6bb768d07", + "Yammer": "00000005-0000-0ff1-ce00-000000000000", } diff --git a/vendor/github.com/manicminer/hamilton/msgraph/accesspackageassignmentrequest.go b/vendor/github.com/manicminer/hamilton/msgraph/accesspackageassignmentrequest.go new file mode 100644 index 0000000000..5ed72fbb0e --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/accesspackageassignmentrequest.go @@ -0,0 +1,180 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/manicminer/hamilton/odata" +) + +type AccessPackageAssignmentRequestClient struct { + BaseClient Client +} + +func NewAccessPackageAssignmentRequestClient(tenantId string) *AccessPackageAssignmentRequestClient { + return &AccessPackageAssignmentRequestClient{ + BaseClient: NewClient(Version10, tenantId), + } +} + +// List will list all access package assignment requests +func (c *AccessPackageAssignmentRequestClient) List(ctx context.Context, query odata.Query) (*[]AccessPackageAssignmentRequest, int, error) { + entity := getEntity(c.BaseClient.ApiVersion) + + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + DisablePaging: query.Top > 0, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: entity, + Params: query.Values(), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("AccessPackageAssignmentRequestClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + AccessPackageAssignmentRequest []AccessPackageAssignmentRequest `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.AccessPackageAssignmentRequest, status, nil +} + +// Get will get an Access Package request +func (c *AccessPackageAssignmentRequestClient) Get(ctx context.Context, id string) (*AccessPackageAssignmentRequest, int, error) { + entity := getEntity(c.BaseClient.ApiVersion) + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", entity, id), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("AccessPackageAssignmentRequestClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var accessPackageAssignmentRequest AccessPackageAssignmentRequest + if err := json.Unmarshal(respBody, &accessPackageAssignmentRequest); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &accessPackageAssignmentRequest, status, nil +} + +// Create will create an access package request +func (c *AccessPackageAssignmentRequestClient) Create(ctx context.Context, accessPackageAssignementRequest AccessPackageAssignmentRequest) (*AccessPackageAssignmentRequest, int, error) { + var status int + entity := getEntity(c.BaseClient.ApiVersion) + body, err := json.Marshal(accessPackageAssignementRequest) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + Body: body, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: entity, + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("AccessPackageAssignmentRequestClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var newAccessPackageAssignmentRequest AccessPackageAssignmentRequest + if err := json.Unmarshal(respBody, &newAccessPackageAssignmentRequest); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &newAccessPackageAssignmentRequest, status, nil +} + +// Delete will delete an access package request +func (c *AccessPackageAssignmentRequestClient) Delete(ctx context.Context, id string) (int, error) { + entity := getEntity(c.BaseClient.ApiVersion) + _, status, _, err := c.BaseClient.Delete(ctx, DeleteHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s", entity, id), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("AccessPackageAssignmentPolicyClient.BaseClient.Delete(): %v", err) + } + + return status, nil + +} + +// Cancel will cancel a request is in a cancellable state +func (c *AccessPackageAssignmentRequestClient) Cancel(ctx context.Context, id string) (int, error) { + var status int + entity := getEntity(c.BaseClient.ApiVersion) + _, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("%s/%s/cancel", entity, id), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("AccessPackageAssignmentRequestClient.BaseClient.Post(): %v", err) + } + + return status, nil +} + +// Reprocess re-processes an access package assignment request +func (c *AccessPackageAssignmentRequestClient) Reprocess(ctx context.Context, id string) (int, error) { + var status int + entity := getEntity(c.BaseClient.ApiVersion) + _, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ValidStatusCodes: []int{http.StatusAccepted}, + Uri: Uri{ + Entity: fmt.Sprintf("/%s/%s/reprocess", entity, id), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("AccessPackageAssignmentRequestClient.BaseClient.Post(): %v", err) + } + + return status, nil +} + +func getEntity(api ApiVersion) string { + if api == VersionBeta { + return "/identityGovernance/entitlementManagement/accessPackageAssignmentRequests" + } + return "/identityGovernance/entitlementManagement/assignmentRequests" +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/administrative_units.go b/vendor/github.com/manicminer/hamilton/msgraph/administrative_units.go index 04b5b3008e..bc35feb30b 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/administrative_units.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/administrative_units.go @@ -241,6 +241,41 @@ func (c *AdministrativeUnitsClient) GetMember(ctx context.Context, administrativ return &data.Id, status, nil } +func (c *AdministrativeUnitsClient) CreateGroup(ctx context.Context, administrativeUnitId string, group *Group) (*Group, int, error) { + var status int + odataTypeGroup := odata.TypeGroup + group.ODataType = &odataTypeGroup + body, err := json.Marshal(group) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + response, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + Body: body, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusCreated}, + Uri: Uri{ + Entity: fmt.Sprintf("/administrativeUnits/%s/members", administrativeUnitId), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("AdministrativeUnitsClient.BaseClient.Post(): %v", err) + } + + defer response.Body.Close() + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var newGroup Group + if err := json.Unmarshal(responseBody, &newGroup); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &newGroup, status, nil +} + // AddMembers adds new members to a AdministrativeUnit. func (c *AdministrativeUnitsClient) AddMembers(ctx context.Context, administrativeUnitId string, members *Members) (int, error) { var status int diff --git a/vendor/github.com/manicminer/hamilton/msgraph/entitlement_role_assignments.go b/vendor/github.com/manicminer/hamilton/msgraph/entitlement_role_assignments.go new file mode 100644 index 0000000000..155e70104c --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/entitlement_role_assignments.go @@ -0,0 +1,134 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/manicminer/hamilton/odata" +) + +// EntitlementRoleAssignmentsClient performs operations on RoleAssignments. +type EntitlementRoleAssignmentsClient struct { + BaseClient Client +} + +// NewEntitlementRoleAssignmentsClient returns a new EntitlementRoleAssignmentsClient +func NewEntitlementRoleAssignmentsClient(tenantId string) *EntitlementRoleAssignmentsClient { + return &EntitlementRoleAssignmentsClient{ + BaseClient: NewClient(Version10, tenantId), + } +} + +// List returns a list of RoleAssignments +func (c *EntitlementRoleAssignmentsClient) List(ctx context.Context, query odata.Query) (*[]UnifiedRoleAssignment, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/roleManagement/entitlementManagement/roleAssignments", + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("EntitlementRoleAssignmentsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + RoleAssignments []UnifiedRoleAssignment `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.RoleAssignments, status, nil +} + +// Get retrieves a UnifiedRoleAssignment +func (c *EntitlementRoleAssignmentsClient) Get(ctx context.Context, id string, query odata.Query) (*UnifiedRoleAssignment, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/roleManagement/entitlementManagement/roleAssignments/%s", id), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("EntitlementRoleAssignmentsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var dirRole UnifiedRoleAssignment + if err := json.Unmarshal(respBody, &dirRole); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &dirRole, status, nil +} + +// Create creates a new UnifiedRoleAssignment. +func (c *EntitlementRoleAssignmentsClient) Create(ctx context.Context, roleAssignment UnifiedRoleAssignment) (*UnifiedRoleAssignment, int, error) { + var status int + + body, err := json.Marshal(roleAssignment) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + Body: body, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/roleManagement/entitlementManagement/roleAssignments", + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("EntitlementRoleAssignmentsClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var newRoleAssignment UnifiedRoleAssignment + if err := json.Unmarshal(respBody, &newRoleAssignment); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &newRoleAssignment, status, nil +} + +// Delete removes a UnifiedRoleAssignment. +func (c *EntitlementRoleAssignmentsClient) Delete(ctx context.Context, id string) (int, error) { + _, status, _, err := c.BaseClient.Delete(ctx, DeleteHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/roleManagement/entitlementManagement/roleAssignments/%s", id), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("RoleAssignments.BaseClient.Get(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/entitlement_role_definitions.go b/vendor/github.com/manicminer/hamilton/msgraph/entitlement_role_definitions.go new file mode 100644 index 0000000000..c032ba5570 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/entitlement_role_definitions.go @@ -0,0 +1,81 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/manicminer/hamilton/odata" +) + +// EntitlementRoleDefinitionsClient performs operations on RoleDefinitions. +type EntitlementRoleDefinitionsClient struct { + BaseClient Client +} + +// NewEntitlementRoleDefinitionsClient returns a new EntitlementRoleDefinitionsClient +func NewEntitlementRoleDefinitionsClient(tenantId string) *EntitlementRoleDefinitionsClient { + return &EntitlementRoleDefinitionsClient{ + BaseClient: NewClient(Version10, tenantId), + } +} + +// List returns a list of RoleDefinitions +func (c *EntitlementRoleDefinitionsClient) List(ctx context.Context, query odata.Query) (*[]UnifiedRoleDefinition, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/roleManagement/entitlementManagement/roleDefinitions", + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("EntitlementRoleDefinitionsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + RoleDefinitions []UnifiedRoleDefinition `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.RoleDefinitions, status, nil +} + +// Get retrieves a UnifiedRoleDefinition +func (c *EntitlementRoleDefinitionsClient) Get(ctx context.Context, id string, query odata.Query) (*UnifiedRoleDefinition, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/roleManagement/entitlementManagement/roleDefinitions/%s", id), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("EntitlementRoleDefinitionsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var dirRole UnifiedRoleDefinition + if err := json.Unmarshal(respBody, &dirRole); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &dirRole, status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/groups.go b/vendor/github.com/manicminer/hamilton/msgraph/groups.go index e1a8faaef1..e6061c6a5c 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/groups.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/groups.go @@ -700,3 +700,34 @@ func (c *GroupsClient) RemoveOwners(ctx context.Context, id string, ownerIds *[] return status, nil } + +func (c *GroupsClient) ListAdministrativeUnitMemberships(ctx context.Context, id string) (*[]AdministrativeUnit, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + OData: odata.Query{ + Select: []string{"id"}, + }, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/groups/%s/memberOf/microsoft.graph.administrativeUnit", id), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("GroupsClient.BaseClient.Get(): %v", err) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Members []AdministrativeUnit `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Members, status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/models.go b/vendor/github.com/manicminer/hamilton/msgraph/models.go index b1c70c4d55..c462f706e8 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/models.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/models.go @@ -13,18 +13,53 @@ import ( ) type AccessPackage struct { - ID *string `json:"id,omitempty"` - Catalog *AccessPackageCatalog `json:"catalog,omitempty"` - CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` - Description *string `json:"description,omitempty"` - DisplayName *string `json:"displayName,omitempty"` - IsHidden *bool `json:"isHidden,omitempty"` - ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty"` - //Beta - IsRoleScopesVisible *bool `json:"isRoleScopesVisible,omitempty"` - ModifiedBy *string `json:"modifiedBy,omitempty"` - CatalogId *string `json:"catalogId,omitempty"` - CreatedBy *string `json:"createdBy,omitempty"` + ID *string `json:"id,omitempty"` + Catalog *AccessPackageCatalog `json:"catalog,omitempty"` + CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` + Description *string `json:"description,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + IsHidden *bool `json:"isHidden,omitempty"` + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty"` + AccessPackageIncompatibleWith *[]AccessPackage `json:"accessPackagesIncompatibleWith,omitempty"` // Relationship + AssignmentPolicies *[]AccessPackageAssignmentPolicy `json:"assignmentPolicies,omitempty"` // Relationship + IncompatibleAccessPackages *[]AccessPackage `json:"incompatibleAccessPackages,omitempty"` // Relationship + IncompatibleGroups *[]Group `json:"incompatibleGroups,omitempty"` // Relationship + IsRoleScopesVisible *bool `json:"isRoleScopesVisible,omitempty"` // beta property + ModifiedBy *string `json:"modifiedBy,omitempty"` // beta property + CatalogId *string `json:"catalogId,omitempty"` // beta property + CreatedBy *string `json:"createdBy,omitempty"` // beta property +} + +type AccessPackageAnswer struct { + DisplayValue *string `json:"displayValue,omitempty"` + AnsweredQuestion *AccessPackageQuestion `json:"answeredQuestion,omitempty"` +} + +type AccessPackageAssignment struct { + ID *string `json:"id,omitempty"` + Target *AccessPackageSubject `json:"target,omitempty"` + TargetID *string `json:"targetId,omitempty"` + AssignementPolicyID *string `json:"assignmentPolicyId,omitempty"` + AccessPackageID *string `json:"accessPackageId,omitempty"` +} + +type AccessPackageAssignmentRequest struct { + ID *string `json:"id,omitempty"` + RequestType *AccessPackageRequestType `json:"requestType,omitempty"` + Status *string `json:"status,omitempty"` + CompletedDateTime *time.Time `json:"completedDateTime,omitempty"` + CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` + State *AccessPackageRequestState `json:"state,omitempty"` + Schedule *EntitlementManagementSchedule `json:"schedule,omitempty"` + Assignment *AccessPackageAssignment `json:"assignment,omitempty"` + AccessPackageAssignment *AccessPackageAssignment `json:"accessPackageAssignment,omitempty"` // beta Relationship + CompletedDate *time.Time `json:"completedDate,omitempty"` // beta property + IsValidationOnly *bool `json:"isValidationOnly,omitempty"` // beta property + Justification *string `json:"justification,omitempty"` // beta property + RequestState *AccessPackageRequestState `json:"requestState,omitempty"` // beta property + RequestStatus *string `json:"requestStatus,omitempty"` // beta property + CustomExtensionHandlerInstance *[]CustomExtensionHandlerInstance `json:"customExtensionHandlerInstances,omitempty"` // beta property + Answers *[]AccessPackageAnswer `json:"answers,omitempty"` // beta property } type AccessPackageAssignmentPolicy struct { @@ -159,6 +194,16 @@ type AccessPackageResourceScope struct { Url *string `json:"url"` } +type AccessPackageSubject struct { + DisplayName *string `json:"displayName,omitempty"` + Email *string `json:"email,omitempty"` + ID *string `json:"id,omitempty"` + ObjectID *string `json:"objectId,omitempty"` + OnPremiseSecurityIdentifier *string `json:"onPremisesSecurityIdentifier,omitempty"` + PrincipalName *string `json:"principalName,omitempty"` + SubjectType *string `json:"subjectType,omitempty"` +} + type AddIn struct { ID *string `json:"id,omitempty"` Properties *[]AddInKeyValue `json:"properties,omitempty"` @@ -213,9 +258,11 @@ type Application struct { IsFallbackPublicClient *bool `json:"isFallbackPublicClient,omitempty"` IsManagementRestricted *bool `json:"isManagementRestricted,omitempty"` KeyCredentials *[]KeyCredential `json:"keyCredentials,omitempty"` - Oauth2RequirePostResponse *bool `json:"oauth2RequirePostResponse,omitempty"` + Oauth2RequirePostResponse *bool `json:"oauth2RequirePostResponse,omitempty"` // field name has typo in beta API + Oauth2RequiredPostResponse *bool `json:"oauth2RequiredPostResponse,omitempty"` OnPremisesPublishing *OnPremisesPublishing `json:"onPremisePublishing,omitempty"` OptionalClaims *OptionalClaims `json:"optionalClaims,omitempty"` + Notes *string `json:"notes,omitempty"` ParentalControlSettings *ParentalControlSettings `json:"parentalControlSettings,omitempty"` PasswordCredentials *[]PasswordCredential `json:"passwordCredentials,omitempty"` PublicClient *PublicClient `json:"publicClient,omitempty"` @@ -498,10 +545,12 @@ type AppRoleAssignment struct { } type ApprovalSettings struct { - IsApprovalRequired *bool `json:"isApprovalRequired,omitempty"` - IsApprovalRequiredForExtension *bool `json:"isApprovalRequiredForExtension,omitempty"` - IsRequestorJustificationRequired *bool `json:"isRequestorJustificationRequired,omitempty"` - ApprovalMode ApprovalMode `json:"approvalMode,omitempty"` + IsApprovalRequiredForAdd *bool `json:"isApprovalRequiredForAdd,omitempty"` + IsApprovalRequiredForUpdate *bool `json:"isApprovalRequiredForUpdate,omitempty"` + IsApprovalRequired *bool `json:"isApprovalRequired,omitempty"` //beta property + IsApprovalRequiredForExtension *bool `json:"isApprovalRequiredForExtension,omitempty"` //beta property + IsRequestorJustificationRequired *bool `json:"isRequestorJustificationRequired,omitempty"` //beta property + ApprovalMode ApprovalMode `json:"approvalMode,omitempty"` //beta property ApprovalStages *[]ApprovalStage `json:"approvalStages,omitempty"` } @@ -620,6 +669,7 @@ type ConditionalAccessPolicy struct { type ConditionalAccessSessionControls struct { ApplicationEnforcedRestrictions *ApplicationEnforcedRestrictionsSessionControl `json:"applicationEnforcedRestrictions,omitempty"` CloudAppSecurity *CloudAppSecurityControl `json:"cloudAppSecurity,omitempty"` + DisableResilienceDefaults *bool `json:"disableResilienceDefaults,omitempty"` PersistentBrowser *PersistentBrowserSessionControl `json:"persistentBrowser,omitempty"` SignInFrequency *SignInFrequencySessionControl `json:"signInFrequency,omitempty"` } @@ -680,6 +730,13 @@ type ConnectedOrganization struct { ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty"` } +type CustomExtensionHandlerInstance struct { + CustomExensionId *string `json:"customExtensionId,omitempty"` + ExternalCorrelationId *string `json:"externalCorrelationId,omitempty"` + Stage *AccessPackageCustomExtensionStage `json:"stage,omitempty"` + Status *AccessPackageCustomExtensionHandlerStatus `json:"status,omitempty"` +} + type DelegatedPermissionGrant struct { Id *string `json:"id,omitempty"` ClientId *string `json:"clientId,omitempty"` @@ -855,11 +912,23 @@ type EmailAuthenticationMethod struct { EmailAddress *string `json:"emailAddress,omitempty"` } +type EntitlementManagementSchedule struct { + StartDateTime *time.Time `json:"startDateTime,omitempty"` + Expiration *ExpirationPattern `json:"expiration,omitempty"` + Recurrence *RecurrencePattern `json:"recurrence,omitempty"` +} + type ExtensionSchemaProperty struct { Name *string `json:"name,omitempty"` Type ExtensionSchemaPropertyDataType `json:"type,omitempty"` } +type ExpirationPattern struct { + Duration *time.Duration `json:"duration,omitempty"` + EndDateTime *time.Time `json:"endDateTime,omitempty"` + Type *ExpirationPatternType `json:"type,omitempty"` +} + type FederatedIdentityCredential struct { Audiences *[]string `json:"audiences,omitempty"` Description *StringNullWhenEmpty `json:"description,omitempty"` @@ -1260,6 +1329,16 @@ type Recipient struct { EmailAddress *EmailAddress `json:"emailAddress,omitempty"` } +type RecurrencePattern struct { + DayOfMonth *int `json:"dayOfMonth,omitempty"` + Interval *int `json:"interval,omitempty"` + Month *int `json:"month,omitempty"` + Type *RecurrencePatternType `json:"type,omitempty"` + DaysOfWeek *[]DaysOfWeekType `json:"daysOfWeek,omitempty"` + FirstDayOfWeek *FirstDayOfWeek `json:"firstDayOfWeek,omitempty"` + Index *IndexType `json:"index,omitempty"` +} + type Ref struct { ObjectUri *string `json:"@odata.id,omitempty"` } @@ -1340,7 +1419,7 @@ type ServicePrincipal struct { PasswordCredentials *[]PasswordCredential `json:"passwordCredentials,omitempty"` PasswordSingleSignOnSettings *PasswordSingleSignOnSettings `json:"passwordSingleSignOnSettings,omitempty"` PreferredSingleSignOnMode *PreferredSingleSignOnMode `json:"preferredSingleSignOnMode,omitempty"` - PreferredTokenSigningKeyThumbprint *string `json:"preferredTokenSigningKeyThumbprint,omitempty"` + PreferredTokenSigningKeyThumbprint *StringNullWhenEmpty `json:"preferredTokenSigningKeyThumbprint,omitempty"` PreferredTokenSigningKeyEndDateTime *time.Time `json:"preferredTokenSigningKeyEndDateTime,omitempty"` PublishedPermissionScopes *[]PermissionScope `json:"publishedPermissionScopes,omitempty"` ReplyUrls *[]string `json:"replyUrls,omitempty"` @@ -1514,6 +1593,37 @@ type TargetResource struct { ModifiedProperties *[]ModifiedProperty `json:"modifiedProperties,omitempty"` } +type TermsOfUseAgreement struct { + ID *string `json:"id,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + UserReacceptRequiredFrequency *string `json:"userReacceptRequiredFrequency,omitempty"` + IsViewingBeforeAcceptanceRequired *bool `json:"isViewingBeforeAcceptanceRequired,omitempty"` + IsPerDeviceAcceptanceRequired *bool `json:"isPerDeviceAcceptanceRequired,omitempty"` + TermsExpiration *TermsOfUseAgreementExpiration `json:"termsExpiration,omitempty"` //For some reason this exports the Frequency both here and above AcceptanceExpirationFrequency Request separates them but response groups them. Anticipate this in Tests + Files *[]TermsOfUseAgreementFile `json:"files,omitempty"` + File *TermsOfUseAgreementFile `json:"file,omitempty"` +} + +type TermsOfUseAgreementExpiration struct { + StartDateTime *time.Time `json:"startDateTime,omitempty"` + Frequency *string `json:"frequency,omitempty"` +} + +type TermsOfUseAgreementFileData struct { + //Data is within its own object for some reason + Data *[]byte `json:"data,omitempty"` +} + +type TermsOfUseAgreementFile struct { + ID *string `json:"id,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + FileName *string `json:"fileName,omitempty"` + Language *string `json:"language,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` + IsMajorVersion *bool `json:"isMajorVersion,omitempty"` + FileData *TermsOfUseAgreementFileData `json:"fileData,omitempty"` +} + type TemporaryAccessPassAuthenticationMethod struct { ID *string `json:"id,omitempty"` TemporaryAccessPass *string `json:"temporaryAccessPass,omitempty"` diff --git a/vendor/github.com/manicminer/hamilton/msgraph/termsofuse.go b/vendor/github.com/manicminer/hamilton/msgraph/termsofuse.go new file mode 100644 index 0000000000..b2af0e7a59 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/termsofuse.go @@ -0,0 +1,150 @@ +package msgraph + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" +) + +// TermsOfUseAgreementClient performs operations on TermsOfUseAgreement. +type TermsOfUseAgreementClient struct { + BaseClient Client +} + +// NewTermsOfUseAgreementClient returns a new TermsOfUseAgreementClient +func NewTermsOfUseAgreementClient(tenantId string) *TermsOfUseAgreementClient { + return &TermsOfUseAgreementClient{ + BaseClient: NewClient(VersionBeta, tenantId), + } +} + +// List returns a list of TermsOfUseAgreement agreements, optionally filtered using OData. +func (c *TermsOfUseAgreementClient) List(ctx context.Context, filter string) (*[]TermsOfUseAgreement, int, error) { + params := url.Values{} + if filter != "" { + params.Add("$filter", filter) + } + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/termsOfUse/agreements", + Params: params, + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("TermsOfUseAgreementClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + var data struct { + TermsOfUseAgreements []TermsOfUseAgreement `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + return &data.TermsOfUseAgreements, status, nil +} + +// Create creates a new TermsOfUse agreement. +func (c *TermsOfUseAgreementClient) Create(ctx context.Context, termsOfUseAgreement TermsOfUseAgreement) (*TermsOfUseAgreement, int, error) { + var status int + body, err := json.Marshal(termsOfUseAgreement) + + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + Body: body, + ValidStatusCodes: []int{http.StatusCreated}, + Uri: Uri{ + Entity: "/identityGovernance/termsOfUse/agreements", + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("TermsOfUseAgreementClient.BaseClient.Post(): %v", err) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + var newTermsOfUseAgreement TermsOfUseAgreement + if err := json.Unmarshal(respBody, &newTermsOfUseAgreement); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + return &newTermsOfUseAgreement, status, nil +} + +// Get retrieves an TermsOfUseAgreement agreement. +func (c *TermsOfUseAgreementClient) Get(ctx context.Context, id string) (*TermsOfUseAgreement, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/termsOfUse/agreements/%s", id), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("TermsOfUseAgreementClient.BaseClient.Get(): %v", err) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + var termsOfUseAgreement TermsOfUseAgreement + if err := json.Unmarshal(respBody, &termsOfUseAgreement); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + return &termsOfUseAgreement, status, nil +} + +// Update amends an existing TermsOfUseAgreement agreement. +func (c *TermsOfUseAgreementClient) Update(ctx context.Context, termsOfUseAgreement TermsOfUseAgreement) (int, error) { + var status int + if termsOfUseAgreement.ID == nil { + return status, errors.New("cannot update TermsOfUseAgreement agreement with nil ID") + } + + body, err := json.Marshal(termsOfUseAgreement) + if err != nil { + return status, fmt.Errorf("json.Marshal(): %v", err) + } + _, status, _, err = c.BaseClient.Patch(ctx, PatchHttpRequestInput{ + Body: body, + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/termsOfUse/agreements/%s", *termsOfUseAgreement.ID), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("TermsOfUseAgreementClient.BaseClient.Patch(): %v", err) + } + return status, nil +} + +// Delete removes a TermsOfUseAgreement agreement. +func (c *TermsOfUseAgreementClient) Delete(ctx context.Context, id string) (int, error) { + _, status, _, err := c.BaseClient.Delete(ctx, DeleteHttpRequestInput{ + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/termsOfUse/agreements/%s", id), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("TermsOfUseAgreementClient.BaseClient.Delete(): %v", err) + } + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go b/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go index 11206c42b8..0b9e579459 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go @@ -43,6 +43,55 @@ const ( AccessPackageCatalogTypeUserManaged AccessPackageCatalogType = "UserManaged" ) +type AccessPackageCustomExtensionStage = string + +const ( + AccessPackageCustomExtensionStageAssignmentRequestCreated AccessPackageCustomExtensionStage = "assignmentRequestCreated" + AccessPackageCustomExtensionStageAssignmentRequestApproved AccessPackageCustomExtensionStage = "assignmentRequestApproved" + AccessPackageCustomExtensionStageAssignmentRequestGranted AccessPackageCustomExtensionStage = "assignmentRequestGranted" + AccessPackageCustomExtensionStageAssignmentRequestRemoved AccessPackageCustomExtensionStage = "assignmentRequestRemoved" + AccessPackageCustomExtensionStageAssignmentFourteenDaysBeforeExpiration AccessPackageCustomExtensionStage = "assignmentFourteenDaysBeforeExpiration" + AccessPackageCustomExtensionStageAssignmentOneDayBeforeExpiration AccessPackageCustomExtensionStage = "assignmentOneDayBeforeExpiration" +) + +type AccessPackageCustomExtensionHandlerStatus = string + +const ( + AccessPackageCustomExtensionHandlerStatusRequestSent AccessPackageCustomExtensionHandlerStatus = "requestSent" + AccessPackageCustomExtensionHandlerStatusRequestReceived AccessPackageCustomExtensionHandlerStatus = "requestReceived" +) + +type AccessPackageRequestState = string + +const ( + AccessPackageRequestStateSubmitted AccessPackageRequestState = "submitted" + AccessPackageRequestStatePendingApproval AccessPackageRequestState = "pendingApproval" + AccessPackageRequestStateDelivering AccessPackageRequestState = "delivering" + AccessPackageRequestStateDelivered AccessPackageRequestState = "delievered" + AccessPackageRequestStateDeliveryFailed AccessPackageRequestState = "deliveryFailed" + AccessPackageRequestStateDenied AccessPackageRequestState = "denied" + AccessPackageRequestStateScheduled AccessPackageRequestState = "scheduled" + AccessPackageRequestStateCanceled AccessPackageRequestState = "canceled" + AccessPackageRequestStatePartiallyDelievered AccessPackageRequestState = "partiallyDelivered" +) + +type AccessPackageRequestType = string + +const ( + AccessPackageRequestTypeNotSpecified AccessPackageRequestType = "notSpecified" + AccessPackageRequestTypeuserAdd AccessPackageRequestType = "userAdd" + AccessPackageRequestTypeUserExtend AccessPackageRequestType = "UserExtend" + AccessPackageRequestTypeUserUpdate AccessPackageRequestType = "userUpdate" + AccessPackageRequestTypeUserRemove AccessPackageRequestType = "userRemove" + AccessPackageRequestTypeAdminAdd AccessPackageRequestType = "adminAdd" + AccessPackageRequestTypeAdminUpdate AccessPackageRequestType = "adminUpdate" + AccessPackageRequestTypeAdminRemove AccessPackageRequestType = "adminRemove" + AccessPackageRequestTypeSystemAdd AccessPackageRequestType = "systemAdd" + AccessPackageRequestTypeSystemUpdate AccessPackageRequestType = "systemUpdate" + AccessPackageRequestTypeSystemRemove AccessPackageRequestType = "systemRemove" + AccessPackageRequestTypeOnBehalfAdd AccessPackageRequestType = "onBehalfAdd" +) + type AccessPackageResourceOriginSystem = string const ( @@ -327,6 +376,18 @@ const ( ConnectedOrganizationStateUnknownFutureValue ConnectedOrganizationState = "unknownFutureValue" ) +type DaysOfWeekType = string + +const ( + DaysOfWeekTypeSunday DaysOfWeekType = "sunday" + DaysOfWeekTypeMonday DaysOfWeekType = "monday" + DaysOfWeekTypeTuesday DaysOfWeekType = "tuesday" + DaysOfWeekTypeWednesday DaysOfWeekType = "wednesday" + DaysOfWeekTypeThursday DaysOfWeekType = "thursday" + DaysOfWeekTypeFriday DaysOfWeekType = "friday" + DaysOfWeekTypeSaturday DaysOfWeekType = "saturday" +) + type DelegatedPermissionGrantConsentType = string const ( @@ -334,6 +395,15 @@ const ( DelegatedPermissionGrantConsentTypePrincipal DelegatedPermissionGrantConsentType = "Principal" ) +type ExpirationPatternType = string + +const ( + ExpirationPatternTypeNotSpecified ExpirationPatternType = "notSpecified" + ExpirationPatternTypeNoExpiration ExpirationPatternType = "noExpiration" + ExpirationPatternTypeAfterDateTime ExpirationPatternType = "afterDateTime" + ExpirationPatternTypeAfterDuration ExpirationPatternType = "afterDuration" +) + type ExtensionSchemaTargetType = string const ( @@ -366,6 +436,18 @@ const ( FeatureTypeUnknownFutureValue FeatureType = "unknownFutureValue" ) +type FirstDayOfWeek = string + +const ( + FirstDayOfWeekSunday FirstDayOfWeek = "sunday" + FirstDayOfWeekMonday FirstDayOfWeek = "monday" + FirstDayOfWeekTuesday FirstDayOfWeek = "tuesday" + FirstDayOfWeekWednesday FirstDayOfWeek = "wednesday" + FirstDayOfWeekThursday FirstDayOfWeek = "thursday" + FirstDayOfWeekFriday FirstDayOfWeek = "friday" + FirstDayOfWeekSaturday FirstDayOfWeek = "staturday" +) + type GroupMembershipRuleProcessingState = string const ( @@ -538,6 +620,17 @@ const ( PreferredSingleSignOnModeSaml PreferredSingleSignOnMode = "saml" ) +type RecurrencePatternType = string + +const ( + RecurrencePatternTypeDaily RecurrencePatternType = "daily" + RecurrencePatternTypeWeekly RecurrencePatternType = "weekly" + RecurrencePatternTypeAbsoluteMonthly RecurrencePatternType = "absoluteMonthly" + RecurrencePatternTypeRelativeMonthly RecurrencePatternType = "relativeMonthly" + RecurrencePatternTypeAbsoluteYearly RecurrencePatternType = "absoluteYearly" + RecurrencePatternTypeRelativeYearly RecurrencePatternType = "relativeYearly" +) + type RegistrationAuthMethod = string const ( @@ -651,3 +744,13 @@ const ( UserflowAttributeDataTypeBoolean UserflowAttributeDataType = "boolean" UserflowAttributeDataTypeInt64 UserflowAttributeDataType = "int64" ) + +type IndexType = string + +const ( + IndexTypeFirst IndexType = "first" + IndexTypeSecond IndexType = "second" + IndexTypeThird IndexType = "third" + IndexTypeFourth IndexType = "fourth" + IndexTypeLast IndexType = "last" +) diff --git a/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go new file mode 100644 index 0000000000..904b57e01d --- /dev/null +++ b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go @@ -0,0 +1,77 @@ +// Copyright 2012 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 pbkdf2 implements the key derivation function PBKDF2 as defined in RFC +2898 / PKCS #5 v2.0. + +A key derivation function is useful when encrypting data based on a password +or any other not-fully-random data. It uses a pseudorandom function to derive +a secure encryption key based on the password. + +While v2.0 of the standard defines only one pseudorandom function to use, +HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved +Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To +choose, you can pass the `New` functions from the different SHA packages to +pbkdf2.Key. +*/ +package pbkdf2 // import "golang.org/x/crypto/pbkdf2" + +import ( + "crypto/hmac" + "hash" +) + +// Key derives a key from the password, salt and iteration count, returning a +// []byte of length keylen that can be used as cryptographic key. The key is +// derived based on the method described as PBKDF2 with the HMAC variant using +// the supplied hash function. +// +// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you +// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by +// doing: +// +// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New) +// +// Remember to get a good random salt. At least 8 bytes is recommended by the +// RFC. +// +// Using a higher iteration count will increase the cost of an exhaustive +// search but will also make derivation proportionally slower. +func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen + + var buf [4]byte + dk := make([]byte, 0, numBlocks*hashLen) + U := make([]byte, hashLen) + for block := 1; block <= numBlocks; block++ { + // N.B.: || means concatenation, ^ means XOR + // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter + // U_1 = PRF(password, salt || uint(i)) + prf.Reset() + prf.Write(salt) + buf[0] = byte(block >> 24) + buf[1] = byte(block >> 16) + buf[2] = byte(block >> 8) + buf[3] = byte(block) + prf.Write(buf[:4]) + dk = prf.Sum(dk) + T := dk[len(dk)-hashLen:] + copy(U, T) + + // U_n = PRF(password, U_(n-1)) + for n := 2; n <= iter; n++ { + prf.Reset() + prf.Write(U) + U = U[:0] + U = prf.Sum(U) + for x := range U { + T[x] ^= U[x] + } + } + } + return dk[:keyLen] +} diff --git a/vendor/golang.org/x/crypto/pkcs12/crypto.go b/vendor/golang.org/x/crypto/pkcs12/crypto.go deleted file mode 100644 index 484ca51b71..0000000000 --- a/vendor/golang.org/x/crypto/pkcs12/crypto.go +++ /dev/null @@ -1,131 +0,0 @@ -// 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/pkcs12.go b/vendor/golang.org/x/crypto/pkcs12/pkcs12.go deleted file mode 100644 index 3a89bdb3e3..0000000000 --- a/vendor/golang.org/x/crypto/pkcs12/pkcs12.go +++ /dev/null @@ -1,360 +0,0 @@ -// 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. -// -// This package is frozen. If it's missing functionality you need, consider -// an alternative like software.sslmate.com/src/go-pkcs12. -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}) - - errUnknownAttributeOID = errors.New("pkcs12: unknown attribute OID") -) - -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 -} - -// ToPEM converts all "safe bags" contained in pfxData to PEM blocks. -// Unknown attributes are discarded. -// -// Note that although the returned PEM blocks for private keys have type -// "PRIVATE KEY", the bytes are not encoded according to PKCS #8, but according -// to PKCS #1 for RSA keys and SEC 1 for ECDSA keys. -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 == errUnknownAttributeOID { - continue - } - 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 "", "", errUnknownAttributeOID - } - - 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; if there are more use ToPEM instead. -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") - return nil, nil, err - } - - 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/modules.txt b/vendor/modules.txt index 73b2674f07..b67d0b330f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -151,7 +151,7 @@ github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 ## explicit; go 1.15 github.com/hashicorp/yamux -# github.com/manicminer/hamilton v0.54.0 +# github.com/manicminer/hamilton v0.57.0 ## explicit; go 1.16 github.com/manicminer/hamilton/auth github.com/manicminer/hamilton/environments @@ -221,8 +221,7 @@ golang.org/x/crypto/openpgp/elgamal golang.org/x/crypto/openpgp/errors golang.org/x/crypto/openpgp/packet golang.org/x/crypto/openpgp/s2k -golang.org/x/crypto/pkcs12 -golang.org/x/crypto/pkcs12/internal/rc2 +golang.org/x/crypto/pbkdf2 golang.org/x/crypto/ssh golang.org/x/crypto/ssh/internal/bcrypt_pbkdf # golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 @@ -361,3 +360,7 @@ google.golang.org/protobuf/types/known/anypb google.golang.org/protobuf/types/known/durationpb google.golang.org/protobuf/types/known/emptypb google.golang.org/protobuf/types/known/timestamppb +# software.sslmate.com/src/go-pkcs12 v0.2.0 +## explicit; go 1.15 +software.sslmate.com/src/go-pkcs12 +software.sslmate.com/src/go-pkcs12/internal/rc2 diff --git a/vendor/software.sslmate.com/src/go-pkcs12/.gitattributes b/vendor/software.sslmate.com/src/go-pkcs12/.gitattributes new file mode 100644 index 0000000000..d2f212e5da --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/.gitattributes @@ -0,0 +1,10 @@ +# Treat all files in this repo as binary, with no git magic updating +# line endings. Windows users contributing to Go will need to use a +# modern version of git and editors capable of LF line endings. +# +# We'll prevent accidental CRLF line endings from entering the repo +# via the git-review gofmt checks. +# +# See golang.org/issue/9281 + +* -text diff --git a/vendor/software.sslmate.com/src/go-pkcs12/.gitignore b/vendor/software.sslmate.com/src/go-pkcs12/.gitignore new file mode 100644 index 0000000000..8339fd61d3 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/.gitignore @@ -0,0 +1,2 @@ +# Add no patterns to .hgignore except for files generated by the build. +last-change diff --git a/vendor/software.sslmate.com/src/go-pkcs12/LICENSE b/vendor/software.sslmate.com/src/go-pkcs12/LICENSE new file mode 100644 index 0000000000..bcecd3d970 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/software.sslmate.com/src/go-pkcs12/README.md b/vendor/software.sslmate.com/src/go-pkcs12/README.md new file mode 100644 index 0000000000..b0af9ab722 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/README.md @@ -0,0 +1,31 @@ +# package pkcs12 + +[![Documentation](https://pkg.go.dev/badge/software.sslmate.com/src/go-pkcs12)](https://pkg.go.dev/software.sslmate.com/src/go-pkcs12) + + import "software.sslmate.com/src/go-pkcs12" + +Package pkcs12 implements some of PKCS#12 (also known as P12 or PFX). +It is intended for decoding DER-encoded P12/PFX files for use with the `crypto/tls` +package, and for encoding P12/PFX files for use by legacy applications which +do not support newer formats. Since PKCS#12 uses weak encryption +primitives, it SHOULD NOT be used for new applications. + +Note that only DER-encoded PKCS#12 files are supported, even though PKCS#12 +allows BER encoding. This is because encoding/asn1 only supports DER. + +This package is forked from `golang.org/x/crypto/pkcs12`, which is frozen. +The implementation is distilled from https://tools.ietf.org/html/rfc7292 +and referenced documents. + +## Import Path + +Note that although the source code and issue tracker for this package are hosted +on GitHub, the import path is: + + software.sslmate.com/src/go-pkcs12 + +Please be sure to use this path when you `go get` and `import` this package. + +## Report Issues / Send Patches + +Open an issue or PR at https://github.com/SSLMate/go-pkcs12 diff --git a/vendor/golang.org/x/crypto/pkcs12/bmp-string.go b/vendor/software.sslmate.com/src/go-pkcs12/bmp-string.go similarity index 77% rename from vendor/golang.org/x/crypto/pkcs12/bmp-string.go rename to vendor/software.sslmate.com/src/go-pkcs12/bmp-string.go index 233b8b62cc..2bfbf2e5a8 100644 --- a/vendor/golang.org/x/crypto/pkcs12/bmp-string.go +++ b/vendor/software.sslmate.com/src/go-pkcs12/bmp-string.go @@ -9,14 +9,27 @@ import ( "unicode/utf16" ) -// bmpString returns s encoded in UCS-2 with a zero terminator. +// bmpStringZeroTerminated returns s encoded in UCS-2 with a zero terminator. +func bmpStringZeroTerminated(s string) ([]byte, error) { + // References: + // https://tools.ietf.org/html/rfc7292#appendix-B.1 + // The above RFC provides the info that BMPStrings are NULL terminated. + + ret, err := bmpString(s) + if err != nil { + return nil, err + } + + return append(ret, 0, 0), nil +} + +// bmpString returns s encoded in UCS-2 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) @@ -27,7 +40,7 @@ func bmpString(s string) ([]byte, error) { ret = append(ret, byte(r/256), byte(r%256)) } - return append(ret, 0, 0), nil + return ret, nil } func decodeBMPString(bmpString []byte) (string, error) { diff --git a/vendor/software.sslmate.com/src/go-pkcs12/crypto.go b/vendor/software.sslmate.com/src/go-pkcs12/crypto.go new file mode 100644 index 0000000000..70425f8307 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/crypto.go @@ -0,0 +1,265 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +// 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/aes" + "crypto/cipher" + "crypto/des" + "crypto/sha1" + "crypto/sha256" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "hash" + + "golang.org/x/crypto/pbkdf2" + "software.sslmate.com/src/go-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}) + oidPBES2 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 5, 13}) + oidPBKDF2 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 5, 12}) + oidHmacWithSHA1 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 2, 7}) + oidHmacWithSHA256 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 2, 9}) + oidAES256CBC = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 1, 42}) +) + +// 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 pbeCipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.Block, []byte, error) { + var cipherType pbeCipher + + switch { + case algorithm.Algorithm.Equal(oidPBEWithSHAAnd3KeyTripleDESCBC): + cipherType = shaWithTripleDESCBC{} + case algorithm.Algorithm.Equal(oidPBEWithSHAAnd40BitRC2CBC): + cipherType = shaWith40BitRC2CBC{} + case algorithm.Algorithm.Equal(oidPBES2): + // rfc7292#appendix-B.1 (the original PKCS#12 PBE) requires passwords formatted as BMPStrings. + // However, rfc8018#section-3 recommends that the password for PBES2 follow ASCII or UTF-8. + // This is also what Windows expects. + // Therefore, we convert the password to UTF-8. + originalPassword, err := decodeBMPString(password) + if err != nil { + return nil, nil, err + } + utf8Password := []byte(originalPassword) + return pbes2CipherFor(algorithm, utf8Password) + default: + return nil, nil, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported") + } + + var params pbeParams + if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil { + return nil, nil, 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, nil, err + } + + return block, iv, nil +} + +func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { + block, iv, err := pbeCipherFor(algorithm, password) + 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 +} + +// PBES2-params ::= SEQUENCE { +// keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}}, +// encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} +// } +type pbes2Params struct { + Kdf pkix.AlgorithmIdentifier + EncryptionScheme pkix.AlgorithmIdentifier +} + +// PBKDF2-params ::= SEQUENCE { +// salt CHOICE { +// specified OCTET STRING, +// otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}} +// }, +// iterationCount INTEGER (1..MAX), +// keyLength INTEGER (1..MAX) OPTIONAL, +// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT +// algid-hmacWithSHA1 +// } +type pbkdf2Params struct { + Salt asn1.RawValue + Iterations int + KeyLength int `asn1:"optional"` + Prf pkix.AlgorithmIdentifier +} + +func pbes2CipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.Block, []byte, error) { + var params pbes2Params + if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil { + return nil, nil, err + } + + if !params.Kdf.Algorithm.Equal(oidPBKDF2) { + return nil, nil, NotImplementedError("kdf algorithm " + params.Kdf.Algorithm.String() + " is not supported") + } + + var kdfParams pbkdf2Params + if err := unmarshal(params.Kdf.Parameters.FullBytes, &kdfParams); err != nil { + return nil, nil, err + } + if kdfParams.Salt.Tag != asn1.TagOctetString { + return nil, nil, errors.New("pkcs12: only octet string salts are supported for pbkdf2") + } + + var prf func() hash.Hash + switch { + case kdfParams.Prf.Algorithm.Equal(oidHmacWithSHA256): + prf = sha256.New + case kdfParams.Prf.Algorithm.Equal(oidHmacWithSHA1): + prf = sha1.New + case kdfParams.Prf.Algorithm.Equal(asn1.ObjectIdentifier([]int{})): + prf = sha1.New + } + + key := pbkdf2.Key(password, kdfParams.Salt.Bytes, kdfParams.Iterations, 32, prf) + iv := params.EncryptionScheme.Parameters.Bytes + + var block cipher.Block + switch { + case params.EncryptionScheme.Algorithm.Equal(oidAES256CBC): + b, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + block = b + default: + return nil, nil, NotImplementedError("pbes2 algorithm " + params.EncryptionScheme.Algorithm.String() + " is not supported") + } + return block, iv, nil +} + +// decryptable abstracts an object that contains ciphertext. +type decryptable interface { + Algorithm() pkix.AlgorithmIdentifier + Data() []byte +} + +func pbEncrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { + block, iv, err := pbeCipherFor(algorithm, password) + if err != nil { + return nil, 0, err + } + + return cipher.NewCBCEncrypter(block, iv), block.BlockSize(), nil +} + +func pbEncrypt(info encryptable, decrypted []byte, password []byte) error { + cbc, blockSize, err := pbEncrypterFor(info.Algorithm(), password) + if err != nil { + return err + } + + psLen := blockSize - len(decrypted)%blockSize + encrypted := make([]byte, len(decrypted)+psLen) + copy(encrypted[:len(decrypted)], decrypted) + copy(encrypted[len(decrypted):], bytes.Repeat([]byte{byte(psLen)}, psLen)) + cbc.CryptBlocks(encrypted, encrypted) + + info.SetData(encrypted) + + return nil +} + +// encryptable abstracts a object that contains ciphertext. +type encryptable interface { + Algorithm() pkix.AlgorithmIdentifier + SetData([]byte) +} diff --git a/vendor/golang.org/x/crypto/pkcs12/errors.go b/vendor/software.sslmate.com/src/go-pkcs12/errors.go similarity index 100% rename from vendor/golang.org/x/crypto/pkcs12/errors.go rename to vendor/software.sslmate.com/src/go-pkcs12/errors.go diff --git a/vendor/golang.org/x/crypto/pkcs12/internal/rc2/rc2.go b/vendor/software.sslmate.com/src/go-pkcs12/internal/rc2/rc2.go similarity index 100% rename from vendor/golang.org/x/crypto/pkcs12/internal/rc2/rc2.go rename to vendor/software.sslmate.com/src/go-pkcs12/internal/rc2/rc2.go diff --git a/vendor/golang.org/x/crypto/pkcs12/mac.go b/vendor/software.sslmate.com/src/go-pkcs12/mac.go similarity index 52% rename from vendor/golang.org/x/crypto/pkcs12/mac.go rename to vendor/software.sslmate.com/src/go-pkcs12/mac.go index 5f38aa7de8..b8a3439fd1 100644 --- a/vendor/golang.org/x/crypto/pkcs12/mac.go +++ b/vendor/software.sslmate.com/src/go-pkcs12/mac.go @@ -1,3 +1,4 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. // 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. @@ -7,8 +8,10 @@ package pkcs12 import ( "crypto/hmac" "crypto/sha1" + "crypto/sha256" "crypto/x509/pkix" "encoding/asn1" + "hash" ) type macData struct { @@ -24,17 +27,25 @@ type digestInfo struct { } var ( - oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}) + oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}) + oidSHA256 = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}) ) func verifyMac(macData *macData, message, password []byte) error { - if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) { + var hFn func() hash.Hash + var key []byte + switch { + case macData.Mac.Algorithm.Algorithm.Equal(oidSHA1): + hFn = sha1.New + key = pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20) + case macData.Mac.Algorithm.Algorithm.Equal(oidSHA256): + hFn = sha256.New + key = pbkdf(sha256Sum, 32, 64, macData.MacSalt, password, macData.Iterations, 3, 32) + default: 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 := hmac.New(hFn, key) mac.Write(message) expectedMAC := mac.Sum(nil) @@ -43,3 +54,17 @@ func verifyMac(macData *macData, message, password []byte) error { } return nil } + +func computeMac(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) + macData.Mac.Digest = mac.Sum(nil) + + return nil +} diff --git a/vendor/golang.org/x/crypto/pkcs12/pbkdf.go b/vendor/software.sslmate.com/src/go-pkcs12/pbkdf.go similarity index 96% rename from vendor/golang.org/x/crypto/pkcs12/pbkdf.go rename to vendor/software.sslmate.com/src/go-pkcs12/pbkdf.go index 5c419d41e3..e6e0c6209a 100644 --- a/vendor/golang.org/x/crypto/pkcs12/pbkdf.go +++ b/vendor/software.sslmate.com/src/go-pkcs12/pbkdf.go @@ -7,6 +7,7 @@ package pkcs12 import ( "bytes" "crypto/sha1" + "crypto/sha256" "math/big" ) @@ -20,6 +21,12 @@ func sha1Sum(in []byte) []byte { return sum[:] } +// sha256Sum returns the SHA-256 hash of in. +func sha256Sum(in []byte) []byte { + sum := sha256.Sum256(in) + return sum[:] +} + // fillWithRepeats returns v*ceiling(len(pattern) / v) bytes consisting of // repeats of pattern. func fillWithRepeats(pattern []byte, v int) []byte { @@ -102,7 +109,7 @@ func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID c := (size + u - 1) / u // 6. For i=1, 2, ..., c, do the following: - A := make([]byte, c*20) + A := make([]byte, c*u) 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, @@ -111,7 +118,7 @@ func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID for j := 1; j < r; j++ { Ai = hash(Ai) } - copy(A[i*20:], Ai[:]) + copy(A[i*u:], Ai[:]) if i < c-1 { // skip on last iteration // B. Concatenate copies of Ai to create a string B of length v diff --git a/vendor/software.sslmate.com/src/go-pkcs12/pkcs12.go b/vendor/software.sslmate.com/src/go-pkcs12/pkcs12.go new file mode 100644 index 0000000000..ba6f6fb5c6 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/pkcs12.go @@ -0,0 +1,757 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +// 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 (also known as P12 or PFX). +// It is intended for decoding DER-encoded P12/PFX files for use with the crypto/tls +// package, and for encoding P12/PFX files for use by legacy applications which +// do not support newer formats. Since PKCS#12 uses weak encryption +// primitives, it SHOULD NOT be used for new applications. +// +// Note that only DER-encoded PKCS#12 files are supported, even though PKCS#12 +// allows BER encoding. This is because encoding/asn1 only supports DER. +// +// This package is forked from golang.org/x/crypto/pkcs12, which is frozen. +// The implementation is distilled from https://tools.ietf.org/html/rfc7292 +// and referenced documents. +package pkcs12 // import "software.sslmate.com/src/go-pkcs12" + +import ( + "crypto/ecdsa" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/hex" + "encoding/pem" + "errors" + "io" +) + +// DefaultPassword is the string "changeit", a commonly-used password for +// PKCS#12 files. Due to the weak encryption used by PKCS#12, it is +// RECOMMENDED that you use DefaultPassword when encoding PKCS#12 files, +// and protect the PKCS#12 files using other means. +const DefaultPassword = "changeit" + +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}) + + oidJavaTrustStore = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 113894, 746875, 1, 1}) + oidAnyExtendedKeyUsage = asn1.ObjectIdentifier([]int{2, 5, 29, 37, 0}) +) + +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 } + +func (i *encryptedContentInfo) SetData(data []byte) { i.EncryptedContent = data } + +type safeBag struct { + Id asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"tag:0,explicit"` + Attributes []pkcs12Attribute `asn1:"set,optional"` +} + +func (bag *safeBag) hasAttribute(id asn1.ObjectIdentifier) bool { + for _, attr := range bag.Attributes { + if attr.Id.Equal(id) { + return true + } + } + return false +} + +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 +} + +func (i *encryptedPrivateKeyInfo) SetData(data []byte) { + i.EncryptedData = data +} + +// 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 +} + +// ToPEM converts all "safe bags" contained in pfxData to PEM blocks. +// +// Deprecated: ToPEM creates invalid PEM blocks (private keys +// are encoded as raw RSA or EC private keys rather than PKCS#8 despite being +// labeled "PRIVATE KEY"). To decode a PKCS#12 file, use DecodeChain instead, +// and use the encoding/pem package to convert to PEM if necessary. +func ToPEM(pfxData []byte, password string) ([]*pem.Block, error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, ErrIncorrectPassword + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 2) + + 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, which must be a DER-encoded PKCS#12 file. This function +// assumes that there is only one certificate and only one private key in the +// pfxData. Since PKCS#12 files often contain more than one certificate, you +// probably want to use DecodeChain instead. +func Decode(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, err error) { + var caCerts []*x509.Certificate + privateKey, certificate, caCerts, err = DecodeChain(pfxData, password) + if len(caCerts) != 0 { + err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU") + } + return +} + +// DecodeChain extracts a certificate, a CA certificate chain, and private key +// from pfxData, which must be a DER-encoded PKCS#12 file. This function assumes that there is at least one certificate +// and only one private key in the pfxData. The first certificate is assumed to +// be the leaf certificate, and subsequent certificates, if any, are assumed to +// comprise the CA certificate chain. +func DecodeChain(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, err error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, nil, nil, err + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 2) + if err != nil { + return nil, nil, nil, err + } + + for _, bag := range bags { + switch { + case bag.Id.Equal(oidCertBag): + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, nil, nil, err + } + certs, err := x509.ParseCertificates(certsData) + if err != nil { + return nil, nil, nil, err + } + if len(certs) != 1 { + err = errors.New("pkcs12: expected exactly one certificate in the certBag") + return nil, nil, nil, err + } + if certificate == nil { + certificate = certs[0] + } else { + caCerts = append(caCerts, certs[0]) + } + + case bag.Id.Equal(oidPKCS8ShroundedKeyBag): + if privateKey != nil { + err = errors.New("pkcs12: expected exactly one key bag") + return nil, nil, nil, err + } + + if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword); err != nil { + return nil, nil, nil, err + } + } + } + + if certificate == nil { + return nil, nil, nil, errors.New("pkcs12: certificate missing") + } + if privateKey == nil { + return nil, nil, nil, errors.New("pkcs12: private key missing") + } + + return +} + +// DecodeTrustStore extracts the certificates from pfxData, which must be a DER-encoded +// PKCS#12 file containing exclusively certificates with attribute 2.16.840.1.113894.746875.1.1, +// which is used by Java to designate a trust anchor. +func DecodeTrustStore(pfxData []byte, password string) (certs []*x509.Certificate, err error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, err + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1) + if err != nil { + return nil, err + } + + for _, bag := range bags { + switch { + case bag.Id.Equal(oidCertBag): + if !bag.hasAttribute(oidJavaTrustStore) { + return nil, errors.New("pkcs12: trust store contains a certificate that is not marked as trusted") + } + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, err + } + parsedCerts, err := x509.ParseCertificates(certsData) + if err != nil { + return nil, err + } + + if len(parsedCerts) != 1 { + err = errors.New("pkcs12: expected exactly one certificate in the certBag") + return nil, err + } + + certs = append(certs, parsedCerts[0]) + + default: + return nil, errors.New("pkcs12: expected only certificate bags") + } + } + + return +} + +func getSafeContents(p12Data, password []byte, expectedItems int) (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 { + if !(len(password) == 2 && password[0] == 0 && password[1] == 0) { + return nil, nil, errors.New("pkcs12: no MAC in data") + } + } else 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) != expectedItems { + 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 +} + +// Encode produces pfxData containing one private key (privateKey), an +// end-entity certificate (certificate), and any number of CA certificates +// (caCerts). +// +// The private key is encrypted with the provided password, but due to the +// weak encryption primitives used by PKCS#12, it is RECOMMENDED that you +// specify a hard-coded password (such as pkcs12.DefaultPassword) and protect +// the resulting pfxData using other means. +// +// The rand argument is used to provide entropy for the encryption, and +// can be set to rand.Reader from the crypto/rand package. +// +// Encode emulates the behavior of OpenSSL's PKCS12_create: it creates two +// SafeContents: one that's encrypted with RC2 and contains the certificates, +// and another that is unencrypted and contains the private key shrouded with +// 3DES The private key bag and the end-entity certificate bag have the +// LocalKeyId attribute set to the SHA-1 fingerprint of the end-entity +// certificate. +func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, password string) (pfxData []byte, err error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, err + } + + var pfx pfxPdu + pfx.Version = 3 + + var certFingerprint = sha1.Sum(certificate.Raw) + var localKeyIdAttr pkcs12Attribute + localKeyIdAttr.Id = oidLocalKeyID + localKeyIdAttr.Value.Class = 0 + localKeyIdAttr.Value.Tag = 17 + localKeyIdAttr.Value.IsCompound = true + if localKeyIdAttr.Value.Bytes, err = asn1.Marshal(certFingerprint[:]); err != nil { + return nil, err + } + + var certBags []safeBag + var certBag *safeBag + if certBag, err = makeCertBag(certificate.Raw, []pkcs12Attribute{localKeyIdAttr}); err != nil { + return nil, err + } + certBags = append(certBags, *certBag) + + for _, cert := range caCerts { + if certBag, err = makeCertBag(cert.Raw, []pkcs12Attribute{}); err != nil { + return nil, err + } + certBags = append(certBags, *certBag) + } + + var keyBag safeBag + keyBag.Id = oidPKCS8ShroundedKeyBag + keyBag.Value.Class = 2 + keyBag.Value.Tag = 0 + keyBag.Value.IsCompound = true + if keyBag.Value.Bytes, err = encodePkcs8ShroudedKeyBag(rand, privateKey, encodedPassword); err != nil { + return nil, err + } + keyBag.Attributes = append(keyBag.Attributes, localKeyIdAttr) + + // Construct an authenticated safe with two SafeContents. + // The first SafeContents is encrypted and contains the cert bags. + // The second SafeContents is unencrypted and contains the shrouded key bag. + var authenticatedSafe [2]contentInfo + if authenticatedSafe[0], err = makeSafeContents(rand, certBags, encodedPassword); err != nil { + return nil, err + } + if authenticatedSafe[1], err = makeSafeContents(rand, []safeBag{keyBag}, nil); err != nil { + return nil, err + } + + var authenticatedSafeBytes []byte + if authenticatedSafeBytes, err = asn1.Marshal(authenticatedSafe[:]); err != nil { + return nil, err + } + + // compute the MAC + pfx.MacData.Mac.Algorithm.Algorithm = oidSHA1 + pfx.MacData.MacSalt = make([]byte, 8) + if _, err = rand.Read(pfx.MacData.MacSalt); err != nil { + return nil, err + } + pfx.MacData.Iterations = 1 + if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); err != nil { + return nil, err + } + + pfx.AuthSafe.ContentType = oidDataContentType + pfx.AuthSafe.Content.Class = 2 + pfx.AuthSafe.Content.Tag = 0 + pfx.AuthSafe.Content.IsCompound = true + if pfx.AuthSafe.Content.Bytes, err = asn1.Marshal(authenticatedSafeBytes); err != nil { + return nil, err + } + + if pfxData, err = asn1.Marshal(pfx); err != nil { + return nil, errors.New("pkcs12: error writing P12 data: " + err.Error()) + } + return +} + +// EncodeTrustStore produces pfxData containing any number of CA certificates +// (certs) to be trusted. The certificates will be marked with a special OID that +// allow it to be used as a Java TrustStore in Java 1.8 and newer. +// +// Due to the weak encryption primitives used by PKCS#12, it is RECOMMENDED that +// you specify a hard-coded password (such as pkcs12.DefaultPassword) and protect +// the resulting pfxData using other means. +// +// The rand argument is used to provide entropy for the encryption, and +// can be set to rand.Reader from the crypto/rand package. +// +// EncodeTrustStore creates a single SafeContents that's encrypted with RC2 +// and contains the certificates. +// +// The Subject of the certificates are used as the Friendly Names (Aliases) +// within the resulting pfxData. If certificates share a Subject, then the +// resulting Friendly Names (Aliases) will be identical, which Java may treat as +// the same entry when used as a Java TrustStore, e.g. with `keytool`. To +// customize the Friendly Names, use EncodeTrustStoreEntries. +func EncodeTrustStore(rand io.Reader, certs []*x509.Certificate, password string) (pfxData []byte, err error) { + var certsWithFriendlyNames []TrustStoreEntry + for _, cert := range certs { + certsWithFriendlyNames = append(certsWithFriendlyNames, TrustStoreEntry{ + Cert: cert, + FriendlyName: cert.Subject.String(), + }) + } + return EncodeTrustStoreEntries(rand, certsWithFriendlyNames, password) +} + +// TrustStoreEntry represents an entry in a Java TrustStore. +type TrustStoreEntry struct { + Cert *x509.Certificate + FriendlyName string +} + +// EncodeTrustStoreEntries produces pfxData containing any number of CA +// certificates (entries) to be trusted. The certificates will be marked with a +// special OID that allow it to be used as a Java TrustStore in Java 1.8 and newer. +// +// This is identical to EncodeTrustStore, but also allows for setting specific +// Friendly Names (Aliases) to be used per certificate, by specifying a slice +// of TrustStoreEntry. +// +// If the same Friendly Name is used for more than one certificate, then the +// resulting Friendly Names (Aliases) in the pfxData will be identical, which Java +// may treat as the same entry when used as a Java TrustStore, e.g. with `keytool`. +// +// Due to the weak encryption primitives used by PKCS#12, it is RECOMMENDED that +// you specify a hard-coded password (such as pkcs12.DefaultPassword) and protect +// the resulting pfxData using other means. +// +// The rand argument is used to provide entropy for the encryption, and +// can be set to rand.Reader from the crypto/rand package. +// +// EncodeTrustStoreEntries creates a single SafeContents that's encrypted +// with RC2 and contains the certificates. +func EncodeTrustStoreEntries(rand io.Reader, entries []TrustStoreEntry, password string) (pfxData []byte, err error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, err + } + + var pfx pfxPdu + pfx.Version = 3 + + var certAttributes []pkcs12Attribute + + extKeyUsageOidBytes, err := asn1.Marshal(oidAnyExtendedKeyUsage) + if err != nil { + return nil, err + } + + // the oidJavaTrustStore attribute contains the EKUs for which + // this trust anchor will be valid + certAttributes = append(certAttributes, pkcs12Attribute{ + Id: oidJavaTrustStore, + Value: asn1.RawValue{ + Class: 0, + Tag: 17, + IsCompound: true, + Bytes: extKeyUsageOidBytes, + }, + }) + + var certBags []safeBag + for _, entry := range entries { + + bmpFriendlyName, err := bmpString(entry.FriendlyName) + if err != nil { + return nil, err + } + + encodedFriendlyName, err := asn1.Marshal(asn1.RawValue{ + Class: 0, + Tag: 30, + IsCompound: false, + Bytes: bmpFriendlyName, + }) + if err != nil { + return nil, err + } + + friendlyName := pkcs12Attribute{ + Id: oidFriendlyName, + Value: asn1.RawValue{ + Class: 0, + Tag: 17, + IsCompound: true, + Bytes: encodedFriendlyName, + }, + } + + certBag, err := makeCertBag(entry.Cert.Raw, append(certAttributes, friendlyName)) + if err != nil { + return nil, err + } + certBags = append(certBags, *certBag) + } + + // Construct an authenticated safe with one SafeContent. + // The SafeContents is encrypted and contains the cert bags. + var authenticatedSafe [1]contentInfo + if authenticatedSafe[0], err = makeSafeContents(rand, certBags, encodedPassword); err != nil { + return nil, err + } + + var authenticatedSafeBytes []byte + if authenticatedSafeBytes, err = asn1.Marshal(authenticatedSafe[:]); err != nil { + return nil, err + } + + // compute the MAC + pfx.MacData.Mac.Algorithm.Algorithm = oidSHA1 + pfx.MacData.MacSalt = make([]byte, 8) + if _, err = rand.Read(pfx.MacData.MacSalt); err != nil { + return nil, err + } + pfx.MacData.Iterations = 1 + if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); err != nil { + return nil, err + } + + pfx.AuthSafe.ContentType = oidDataContentType + pfx.AuthSafe.Content.Class = 2 + pfx.AuthSafe.Content.Tag = 0 + pfx.AuthSafe.Content.IsCompound = true + if pfx.AuthSafe.Content.Bytes, err = asn1.Marshal(authenticatedSafeBytes); err != nil { + return nil, err + } + + if pfxData, err = asn1.Marshal(pfx); err != nil { + return nil, errors.New("pkcs12: error writing P12 data: " + err.Error()) + } + return +} + +func makeCertBag(certBytes []byte, attributes []pkcs12Attribute) (certBag *safeBag, err error) { + certBag = new(safeBag) + certBag.Id = oidCertBag + certBag.Value.Class = 2 + certBag.Value.Tag = 0 + certBag.Value.IsCompound = true + if certBag.Value.Bytes, err = encodeCertBag(certBytes); err != nil { + return nil, err + } + certBag.Attributes = attributes + return +} + +func makeSafeContents(rand io.Reader, bags []safeBag, password []byte) (ci contentInfo, err error) { + var data []byte + if data, err = asn1.Marshal(bags); err != nil { + return + } + + if password == nil { + ci.ContentType = oidDataContentType + ci.Content.Class = 2 + ci.Content.Tag = 0 + ci.Content.IsCompound = true + if ci.Content.Bytes, err = asn1.Marshal(data); err != nil { + return + } + } else { + randomSalt := make([]byte, 8) + if _, err = rand.Read(randomSalt); err != nil { + return + } + + var algo pkix.AlgorithmIdentifier + algo.Algorithm = oidPBEWithSHAAnd40BitRC2CBC + if algo.Parameters.FullBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: 2048}); err != nil { + return + } + + var encryptedData encryptedData + encryptedData.Version = 0 + encryptedData.EncryptedContentInfo.ContentType = oidDataContentType + encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm = algo + if err = pbEncrypt(&encryptedData.EncryptedContentInfo, data, password); err != nil { + return + } + + ci.ContentType = oidEncryptedDataContentType + ci.Content.Class = 2 + ci.Content.Tag = 0 + ci.Content.IsCompound = true + if ci.Content.Bytes, err = asn1.Marshal(encryptedData); err != nil { + return + } + } + return +} diff --git a/vendor/golang.org/x/crypto/pkcs12/safebags.go b/vendor/software.sslmate.com/src/go-pkcs12/safebags.go similarity index 54% rename from vendor/golang.org/x/crypto/pkcs12/safebags.go rename to vendor/software.sslmate.com/src/go-pkcs12/safebags.go index def1f7b98d..be83a49768 100644 --- a/vendor/golang.org/x/crypto/pkcs12/safebags.go +++ b/vendor/software.sslmate.com/src/go-pkcs12/safebags.go @@ -1,3 +1,4 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. // 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. @@ -8,6 +9,7 @@ import ( "crypto/x509" "encoding/asn1" "errors" + "io" ) var ( @@ -45,6 +47,36 @@ func decodePkcs8ShroudedKeyBag(asn1Data, password []byte) (privateKey interface{ return privateKey, nil } +func encodePkcs8ShroudedKeyBag(rand io.Reader, privateKey interface{}, password []byte) (asn1Data []byte, err error) { + var pkData []byte + if pkData, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil { + return nil, errors.New("pkcs12: error encoding PKCS#8 private key: " + err.Error()) + } + + randomSalt := make([]byte, 8) + if _, err = rand.Read(randomSalt); err != nil { + return nil, errors.New("pkcs12: error reading random salt: " + err.Error()) + } + var paramBytes []byte + if paramBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: 2048}); err != nil { + return nil, errors.New("pkcs12: error encoding params: " + err.Error()) + } + + var pkinfo encryptedPrivateKeyInfo + pkinfo.AlgorithmIdentifier.Algorithm = oidPBEWithSHAAnd3KeyTripleDESCBC + pkinfo.AlgorithmIdentifier.Parameters.FullBytes = paramBytes + + if err = pbEncrypt(&pkinfo, pkData, password); err != nil { + return nil, errors.New("pkcs12: error encrypting PKCS#8 shrouded key bag: " + err.Error()) + } + + if asn1Data, err = asn1.Marshal(pkinfo); err != nil { + return nil, errors.New("pkcs12: error encoding PKCS#8 shrouded key bag: " + err.Error()) + } + + return asn1Data, nil +} + func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) { bag := new(certBag) if err := unmarshal(asn1Data, bag); err != nil { @@ -55,3 +87,13 @@ func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) { } return bag.Data, nil } + +func encodeCertBag(x509Certificates []byte) (asn1Data []byte, err error) { + var bag certBag + bag.Id = oidCertTypeX509Certificate + bag.Data = x509Certificates + if asn1Data, err = asn1.Marshal(bag); err != nil { + return nil, errors.New("pkcs12: error encoding cert bag: " + err.Error()) + } + return asn1Data, nil +}