diff --git a/changes/23760-pkcs8-apns-key b/changes/23760-pkcs8-apns-key new file mode 100644 index 0000000000..b1e16cbf4c --- /dev/null +++ b/changes/23760-pkcs8-apns-key @@ -0,0 +1 @@ +Allow APNS key to be in unencrypted PKCS8 format, which may happen when migrating from another MDM. diff --git a/server/mdm/apple/cert.go b/server/mdm/apple/cert.go index a0393122d2..6a740b8d84 100644 --- a/server/mdm/apple/cert.go +++ b/server/mdm/apple/cert.go @@ -2,6 +2,7 @@ package apple_mdm import ( "bytes" + "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -60,7 +61,7 @@ func GenerateAPNSCSRKey(email, org string) (*x509.CertificateRequest, *rsa.Priva return certReq, key, nil } -func GenerateAPNSCSR(org, email string, key *rsa.PrivateKey) (*x509.CertificateRequest, error) { +func GenerateAPNSCSR(org, email string, key crypto.PrivateKey) (*x509.CertificateRequest, error) { subj := pkix.Name{ Organization: []string{org}, ExtraNames: []pkix.AttributeTypeAndValue{{ diff --git a/server/service/mdm.go b/server/service/mdm.go index 41fc1f789e..44759b8dc6 100644 --- a/server/service/mdm.go +++ b/server/service/mdm.go @@ -3,6 +3,9 @@ package service import ( "bytes" "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "crypto/tls" "crypto/x509" @@ -2393,7 +2396,7 @@ func (svc *Service) GetMDMAppleCSR(ctx context.Context) ([]byte, error) { } // Check if we have existing certs and keys - var apnsKey *rsa.PrivateKey + var apnsKey crypto.PrivateKey savedAssets, err := svc.ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{ fleet.MDMAssetCACert, fleet.MDMAssetCAKey, @@ -2415,17 +2418,18 @@ func (svc *Service) GetMDMAppleCSR(ctx context.Context) ([]byte, error) { return nil, ctxerr.Wrap(ctx, err, "generate SCEP cert and key") } - apnsKey, err = apple_mdm.NewPrivateKey() + apnsRSAKey, err := apple_mdm.NewPrivateKey() if err != nil { return nil, ctxerr.Wrap(ctx, err, "generate new apns private key") } + apnsKey = apnsRSAKey // Store our config assets encrypted var assets []fleet.MDMConfigAsset for k, v := range map[fleet.MDMAssetName][]byte{ fleet.MDMAssetCACert: apple_mdm.EncodeCertPEM(scepCert), fleet.MDMAssetCAKey: apple_mdm.EncodePrivateKeyPEM(scepKey), - fleet.MDMAssetAPNSKey: apple_mdm.EncodePrivateKeyPEM(apnsKey), + fleet.MDMAssetAPNSKey: apple_mdm.EncodePrivateKeyPEM(apnsRSAKey), } { assets = append(assets, fleet.MDMConfigAsset{ Name: k, @@ -2439,9 +2443,9 @@ func (svc *Service) GetMDMAppleCSR(ctx context.Context) ([]byte, error) { } else { rawApnsKey := savedAssets[fleet.MDMAssetAPNSKey] block, _ := pem.Decode(rawApnsKey.Value) - apnsKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + apnsKey, err = parseAPNSPrivateKey(ctx, block) if err != nil { - return nil, ctxerr.Wrap(ctx, err, "unmarshaling saved apns key") + return nil, err } } @@ -2488,6 +2492,31 @@ func (svc *Service) GetMDMAppleCSR(ctx context.Context) ([]byte, error) { return signedCSRB64, nil } +func parseAPNSPrivateKey(ctx context.Context, block *pem.Block) (crypto.PrivateKey, error) { + if block == nil { + return nil, ctxerr.New(ctx, "failed to decode saved APNS key") + } + + // The code below is based on tls.parsePrivateKey + // https://cs.opensource.google/go/go/+/release-branch.go1.23:src/crypto/tls/tls.go;l=355-372 + if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: + return key, nil + default: + return nil, errors.New("unmarshaled PKCS8 APNS key is not an RSA, ECDSA, or Ed25519 private key") + } + } + if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil { + return key, nil + } + + return nil, ctxerr.New(ctx, fmt.Sprintf("failed to parse APNS private key of type %s", block.Type)) +} + //////////////////////////////////////////////////////////////////////////////// // POST /mdm/apple/apns_certificate //////////////////////////////////////////////////////////////////////////////// diff --git a/server/service/mdm_test.go b/server/service/mdm_test.go index e763cf6c05..ef0821cecf 100644 --- a/server/service/mdm_test.go +++ b/server/service/mdm_test.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "database/sql" + "encoding/pem" "errors" "math/big" "net/http" @@ -2158,3 +2159,44 @@ func TestBatchSetMDMProfilesLabels(t *testing.T) { assert.Equal(t, ProfileLabels{IncludeAny: true}, *profileLabels["DIncAny"]) assert.Equal(t, ProfileLabels{ExcludeAny: true}, *profileLabels["DExclAny"]) } + +func TestParseAPNSPrivateKey(t *testing.T) { + t.Parallel() + // nil block not allowed + ctx := context.Background() + _, err := parseAPNSPrivateKey(ctx, nil) + assert.ErrorContains(t, err, "failed to decode") + + // encrypted pkcs8 not supported + pkcs8Encrypted, err := os.ReadFile("testdata/pkcs8-encrypted.key") + require.NoError(t, err) + block, _ := pem.Decode(pkcs8Encrypted) + assert.NotNil(t, block) + _, err = parseAPNSPrivateKey(ctx, block) + assert.ErrorContains(t, err, "failed to parse APNS private key of type ENCRYPTED PRIVATE KEY") + + // X25519 pkcs8 not supported + pkcs8Encrypted, err = os.ReadFile("testdata/pkcs8-x25519.key") + require.NoError(t, err) + block, _ = pem.Decode(pkcs8Encrypted) + assert.NotNil(t, block) + _, err = parseAPNSPrivateKey(ctx, block) + assert.ErrorContains(t, err, "unmarshaled PKCS8 APNS key is not") + + // In this test, the pkcs1 key and pkcs8 keys are the same key, just different formats + pkcs1, err := os.ReadFile("testdata/pkcs1.key") + require.NoError(t, err) + block, _ = pem.Decode(pkcs1) + assert.NotNil(t, block) + pkcs1Key, err := parseAPNSPrivateKey(ctx, block) + require.NoError(t, err) + + pkcs8, err := os.ReadFile("testdata/pkcs8-rsa.key") + require.NoError(t, err) + block, _ = pem.Decode(pkcs8) + assert.NotNil(t, block) + pkcs8Key, err := parseAPNSPrivateKey(ctx, block) + require.NoError(t, err) + + assert.Equal(t, pkcs1Key, pkcs8Key) +} diff --git a/server/service/testdata/pkcs1.key b/server/service/testdata/pkcs1.key new file mode 100644 index 0000000000..dd9c80bad7 --- /dev/null +++ b/server/service/testdata/pkcs1.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAlfWPfYkhMEbyhaMh/BEAsqpYVh9WkS3e3Ru/QbSzracRrSd+ +OfIsWSrPSCS+TvazQ9K8WkyciqVPawlOSF5G8eeqSSvzBlS8FLCuEYXk6OJMuGpT +2ccm9oFfhOeTWIkmcoKRCw4aHhpAEQRw9BPd5ks3YmxqEz8DebT55Wv/TegMWeRR +AV+CHCjHgoF+0woCRJG9+0sPxhKxKITLjU1tNvkaEoyjaEEXs/MjlRlKlzvBcOm8 +kewd44QEv1iU1ne0q4yUQZA4f6WHKrQ/Wc7bQNnOP8LUkX1PS4TP35no8m+3pj02 +Bg0S2uQzJW24Kj7ge0aHG0rdTEPwXWcUuMKYEwIDAQABAoIBACNWU+NlspvxRAoo +yIPhL9RqXtmgleItOITLGpUts1+a7IfstAqMnlW+yEy8V9o8uT6kxLzUq9jiHqZe +8YMNMFOl31NR13lo808+2S4I6snUNfcJG0xI6U4Sa4NinK6y1wwJ54r+3jm0vt8j +uEhT/CjeFaWMXay+Xx/JfU/KVMWzKgZ+fOkzGauDPHzKQvC8wCs8XZLOOcSAUfBU +zjG3D338utMjUEQPvNsVRBuxpRmvXxhs3+41rrPpb5jr/97fatgn9GVdXciX6Iug +sts2McE6EYllYRhLELqwS72VPBVklL6qtK3I2IDOPiA380LzHe8uUKh8+MWxysV3 +R3dbMSkCgYEAx3BQiPG0O0bjtaVy08UqBuOQtH2C8Rd/SEc9Mx3gP2akwRiJQEl9 +1AX45lVn4Ak91MEFIFtA7+uWpj9YY+vYi+7nByZh8v1CPukIsDLqyLdbjlKMXIfk +JgBbsLi9X8WD+ZJqzNJJ5sl9nZhJQXnOR/KqKtHEA82bL7AVmUKyeDkCgYEAwHzt +EHsmt1YWdv0DRJpTLIlzRO6YxqgcCN24TqtUdbr5pu4Xp1OTWelnBQvx88DZZtny +vn4R/0ERp7I1oyfqwt1OSpZj0dJ9pzs1pGNR5IN6ktn14wULnR44nzdKC8vtwRca +2mD3W9BxAPF0RZMVs0Il4/pUgrSZJXmKZAUKmqsCgYBzscHmT2CganU6MKo2E1BY +Arc1HQJS3bCmuzzTZGdsIB0/Lv6f3oIx9uz2oWDK5FGMFBTnOkinZWFVO7YI8Q4D +zykSSr/Td/twBztVC5SLr2JbY6OtId/F87s8/05MOoZexwNRokPgr85BF9CXPRPf +RPRWNq2hDysRNPKyn7BscQKBgQCzE8ZNXfVP/YRa4NqoeVbGCX429C8oKoAic3wj +nFuwR+o5NirFXgipTm6skEdh+dmISV3/gTRnH/c356DlEJ7k+ZT76Vwmeciyy3AF +Zu1qRjPD6YlL0obSfqo43exCe0/PIxq1bh7SyWJIeVot3EAFDJZJubvj+fa8mY15 +rgMGjwKBgQC5dLj3XinPAMfSe8ZOsD9xZN5t9gSQmYQ2tvQxj0aAT0dWNlqCx5N0 +g8W99LX11lzfORB3e2McgJHsaRRjumbFtwDEkDHj4A1GneuZdCEW879c+Gl3pckJ +726WrUCUWeDJgDnaGJYBqC/UbfHEqYwOksHpZMvjbLxL/+3wOYbv3A== +-----END RSA PRIVATE KEY----- diff --git a/server/service/testdata/pkcs8-encrypted.key b/server/service/testdata/pkcs8-encrypted.key new file mode 100644 index 0000000000..d037536501 --- /dev/null +++ b/server/service/testdata/pkcs8-encrypted.key @@ -0,0 +1,18 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIC3TBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIkErtXjGCalMCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBApOUG3MKrBC/5bDBH/s5VfBIIC +gN5o1aJxvJYbp2oA/quz+BnCFn8ts3wPPOcqarHddy0L/VH3BdqFNbnPZEaDnvDl +kqChRsti4AAeX18ItMeAyNLNFv0J4mfI8Q5E7iEnPp+dTsZqNfVIJe2NGxOS7zp2 +oQQIZVgjW0akDehv6ZDN796qDBlMY2g80wbBrzVgMJu/byG9IQQjngUE9QNGwrsj +7lYSprxjfTZOk1aGBD0d/HsmetIJvCeJ2i/5xAiGgZRrSWMC6aN7Zlra3eIvHQTB +aKZ8/0IT3iKSr6FpkEopOQae8biiTEVGw9D339P3qOSs2ChWWF+OV2sEA67w6q5j +pz6Poc5jgq4FOcf06GdcVa4tst2uykNJCW0wHpcUR1Tr9ILLhrZPYBYVQWW53Eee +o4+mqW2YORdG3a/jLHpEjL0Vdg95QNpdZoMv8plotN1MUBLebd05aCe5hJUb/x74 +3GTwmRGmKoHOhOO3hhUaMCmZIg1xPlNT3jqxrZDoATBeONbaFP8OOkeucVYHbdUO +Ad7z6J8XuZDoxM0BVrGykEiQL2nAOccdsGoC33C9hjkqgU8G9jWElbynJlVqv+1a +lFHWjX5lB6ueiY/rClpVlLmXGB83OVPlo70FV0B9rhRChyB1IJJRYPFDJHSHJNu+ +Pqay8zw82Gh/G+TWH/JCLm5YjX55ZSFMUmvwOLaxyQpmAGNH6dIBTAaSctVA7UrV +jm7m+5T7seiNYNEi19vDJipgr0GbX8+np47VrsJDxsS20wTeA/9ltD0xXwNrEKHd +2Nv/1OaCgnBQHIGULgEn9pT3/vU87bBHYjVdrWoUmqd9zFYtdImQE9u8IKTxTe4R +UPRGHqltz4uOjbD1epkSGe0= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/server/service/testdata/pkcs8-rsa.key b/server/service/testdata/pkcs8-rsa.key new file mode 100644 index 0000000000..939f002077 --- /dev/null +++ b/server/service/testdata/pkcs8-rsa.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCV9Y99iSEwRvKF +oyH8EQCyqlhWH1aRLd7dG79BtLOtpxGtJ3458ixZKs9IJL5O9rND0rxaTJyKpU9r +CU5IXkbx56pJK/MGVLwUsK4RheTo4ky4alPZxyb2gV+E55NYiSZygpELDhoeGkAR +BHD0E93mSzdibGoTPwN5tPnla/9N6AxZ5FEBX4IcKMeCgX7TCgJEkb37Sw/GErEo +hMuNTW02+RoSjKNoQRez8yOVGUqXO8Fw6byR7B3jhAS/WJTWd7SrjJRBkDh/pYcq +tD9ZzttA2c4/wtSRfU9LhM/fmejyb7emPTYGDRLa5DMlbbgqPuB7RocbSt1MQ/Bd +ZxS4wpgTAgMBAAECggEAI1ZT42Wym/FECijIg+Ev1Gpe2aCV4i04hMsalS2zX5rs +h+y0CoyeVb7ITLxX2jy5PqTEvNSr2OIepl7xgw0wU6XfU1HXeWjzTz7ZLgjqydQ1 +9wkbTEjpThJrg2KcrrLXDAnniv7eObS+3yO4SFP8KN4VpYxdrL5fH8l9T8pUxbMq +Bn586TMZq4M8fMpC8LzAKzxdks45xIBR8FTOMbcPffy60yNQRA+82xVEG7GlGa9f +GGzf7jWus+lvmOv/3t9q2Cf0ZV1dyJfoi6Cy2zYxwToRiWVhGEsQurBLvZU8FWSU +vqq0rcjYgM4+IDfzQvMd7y5QqHz4xbHKxXdHd1sxKQKBgQDHcFCI8bQ7RuO1pXLT +xSoG45C0fYLxF39IRz0zHeA/ZqTBGIlASX3UBfjmVWfgCT3UwQUgW0Dv65amP1hj +69iL7ucHJmHy/UI+6QiwMurIt1uOUoxch+QmAFuwuL1fxYP5kmrM0knmyX2dmElB +ec5H8qoq0cQDzZsvsBWZQrJ4OQKBgQDAfO0Qeya3VhZ2/QNEmlMsiXNE7pjGqBwI +3bhOq1R1uvmm7henU5NZ6WcFC/HzwNlm2fK+fhH/QRGnsjWjJ+rC3U5KlmPR0n2n +OzWkY1Hkg3qS2fXjBQudHjifN0oLy+3BFxraYPdb0HEA8XRFkxWzQiXj+lSCtJkl +eYpkBQqaqwKBgHOxweZPYKBqdTowqjYTUFgCtzUdAlLdsKa7PNNkZ2wgHT8u/p/e +gjH27PahYMrkUYwUFOc6SKdlYVU7tgjxDgPPKRJKv9N3+3AHO1ULlIuvYltjo60h +38Xzuzz/Tkw6hl7HA1GiQ+CvzkEX0Jc9E99E9FY2raEPKxE08rKfsGxxAoGBALMT +xk1d9U/9hFrg2qh5VsYJfjb0LygqgCJzfCOcW7BH6jk2KsVeCKlObqyQR2H52YhJ +Xf+BNGcf9zfnoOUQnuT5lPvpXCZ5yLLLcAVm7WpGM8PpiUvShtJ+qjjd7EJ7T88j +GrVuHtLJYkh5Wi3cQAUMlkm5u+P59ryZjXmuAwaPAoGBALl0uPdeKc8Ax9J7xk6w +P3Fk3m32BJCZhDa29DGPRoBPR1Y2WoLHk3SDxb30tfXWXN85EHd7YxyAkexpFGO6 +ZsW3AMSQMePgDUad65l0IRbzv1z4aXelyQnvbpatQJRZ4MmAOdoYlgGoL9Rt8cSp +jA6Swelky+NsvEv/7fA5hu/c +-----END PRIVATE KEY----- diff --git a/server/service/testdata/pkcs8-x25519.key b/server/service/testdata/pkcs8-x25519.key new file mode 100644 index 0000000000..c5b5a224ed --- /dev/null +++ b/server/service/testdata/pkcs8-x25519.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VuBCIEIIBy1s9qwcgQqJsrkOeQ9W5smjPait9cF8LHDcYQt/BK +-----END PRIVATE KEY-----