rnp 1.0.3 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rnp/rnp.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # (c) 2018 Ribose Inc.
3
+ # (c) 2018,2019 Ribose Inc.
4
4
 
5
5
  require 'English'
6
6
  require 'json'
@@ -14,6 +14,7 @@ require 'rnp/key'
14
14
  require 'rnp/op/sign'
15
15
  require 'rnp/op/verify'
16
16
  require 'rnp/op/encrypt'
17
+ require "rnp/op/generate"
17
18
 
18
19
  # Class used for interacting with RNP.
19
20
  class Rnp
@@ -128,6 +129,89 @@ class Rnp
128
129
  end
129
130
  end
130
131
 
132
+ # Generate an RSA key (w/optional subkey).
133
+ #
134
+ # @param userid [String] the userid for the key
135
+ # @param bits [Integer] the bit length for the primary key
136
+ # @param subbits [Integer] the bit length for the subkey
137
+ # (0 if no subkey should be generated)
138
+ # @param password [String] the password to protect the key(s)
139
+ # (nil for no protection)
140
+ def generate_rsa(userid:, bits:, subbits: 0, password:)
141
+ pptr = FFI::MemoryPointer.new(:pointer)
142
+ Rnp.call_ffi(:rnp_generate_key_rsa, @ptr, bits, subbits, userid, password,
143
+ pptr)
144
+ pkey = pptr.read_pointer
145
+ Key.new(pkey) unless pkey.null?
146
+ end
147
+
148
+ # Generate a DSA (w/optional ElGamal subkey) key.
149
+ #
150
+ # @param userid [String] the userid for the key
151
+ # @param bits [Integer] the bit length for the primary key
152
+ # @param subbits [Integer] the bit length for the subkey
153
+ # (0 if no subkey should be generated)
154
+ # @param password [String] the password to protect the key(s)
155
+ # (nil for no protection)
156
+ def generate_dsa_elgamal(userid:, bits:, subbits: 0, password:)
157
+ pptr = FFI::MemoryPointer.new(:pointer)
158
+ Rnp.call_ffi(:rnp_generate_key_dsa_eg, @ptr, bits, subbits, userid,
159
+ password, pptr)
160
+ pkey = pptr.read_pointer
161
+ Key.new(pkey) unless pkey.null?
162
+ end
163
+
164
+ # Generate an ECDSA+ECDH key pair.
165
+ #
166
+ # @param userid [String] the userid for the key
167
+ # @param curve [String] the name of the curve
168
+ # @param password [String] the password to protect the key(s)
169
+ # (nil for no protection)
170
+ def generate_ecdsa_ecdh(userid:, curve:, password:)
171
+ pptr = FFI::MemoryPointer.new(:pointer)
172
+ Rnp.call_ffi(:rnp_generate_key_ec, @ptr, curve, userid, password, pptr)
173
+ pkey = pptr.read_pointer
174
+ Key.new(pkey) unless pkey.null?
175
+ end
176
+
177
+ # Generate an EdDSA+x25519 key pair.
178
+ #
179
+ # @param userid [String] the userid for the key
180
+ # @param password [String] the password to protect the key(s)
181
+ # (nil for no protection)
182
+ def generate_eddsa_25519(userid:, password:)
183
+ pptr = FFI::MemoryPointer.new(:pointer)
184
+ Rnp.call_ffi(:rnp_generate_key_25519, @ptr, userid, password, pptr)
185
+ pkey = pptr.read_pointer
186
+ Key.new(pkey) unless pkey.null?
187
+ end
188
+
189
+ # Generate an SM2 key pair.
190
+ #
191
+ # @param userid [String] the userid for the key
192
+ # @param password [String] the password to protect the key(s)
193
+ # (nil for no protection)
194
+ def generate_sm2(userid:, password:)
195
+ pptr = FFI::MemoryPointer.new(:pointer)
196
+ Rnp.call_ffi(:rnp_generate_key_sm2, @ptr, userid, password, pptr)
197
+ pkey = pptr.read_pointer
198
+ Key.new(pkey) unless pkey.null?
199
+ end
200
+
201
+ # Generate a key and optional subkey.
202
+ #
203
+ # @param userid [String] the userid for the key
204
+ # @param password [String] the password to protect the key(s)
205
+ # (nil for no protection)
206
+ def generate(type:, userid:, bits:, curve: nil, password:,
207
+ subtype: nil, subbits: 0, subcurve: nil)
208
+ pptr = FFI::MemoryPointer.new(:pointer)
209
+ Rnp.call_ffi(:rnp_generate_key_ex, @ptr, type, subtype, bits, subbits,
210
+ curve, subcurve, userid, password, pptr)
211
+ pkey = pptr.read_pointer
212
+ Key.new(pkey) unless pkey.null?
213
+ end
214
+
131
215
  # Load keys.
132
216
  #
133
217
  # @param format [String] the format of the keys to load (GPG, KBX, G10).
@@ -141,6 +225,13 @@ class Rnp
141
225
  Rnp.call_ffi(:rnp_load_keys, @ptr, format, input.ptr, flags)
142
226
  end
143
227
 
228
+ def unload_keys(public_keys: true, secret_keys: true)
229
+ raise ArgumentError, "At least one of public_keys or secret_keys must be true" \
230
+ if !public_keys && !secret_keys
231
+ flags = unload_keys_flags(public_keys: public_keys, secret_keys: secret_keys)
232
+ Rnp.call_ffi(:rnp_unload_keys, @ptr, flags)
233
+ end
234
+
144
235
  # Save keys.
145
236
  #
146
237
  # @param format [String] the format to save the keys in (GPG, KBX, G10).
@@ -349,16 +440,19 @@ class Rnp
349
440
  # @param armored (see Encrypt#armored=)
350
441
  # @param compression (see Encrypt#compression=)
351
442
  # @param cipher (see Encrypt#cipher=)
443
+ # @param aead (see Encrypt#aead=)
352
444
  def encrypt(input:, output: nil, recipients:,
353
445
  armored: nil,
354
446
  compression: nil,
355
- cipher: nil)
447
+ cipher: nil,
448
+ aead: nil)
356
449
  Output.default(output) do |output_|
357
450
  enc = start_encrypt(input: input, output: output_)
358
451
  enc.options = {
359
452
  armored: armored,
360
453
  compression: compression,
361
- cipher: cipher
454
+ cipher: cipher,
455
+ aead: aead,
362
456
  }
363
457
  simple_encrypt(enc, recipients: recipients)
364
458
  end
@@ -373,6 +467,7 @@ class Rnp
373
467
  # @param armored (see Encrypt#armored=)
374
468
  # @param compression (see Encrypt#compression=)
375
469
  # @param cipher (see Encrypt#cipher=)
470
+ # @param aead (see Encrypt#aead=)
376
471
  # @param hash (see Encrypt#hash=)
377
472
  # @param creation_time (see Encrypt#creation_time=)
378
473
  # @param expiration_time (see Encrypt#expiration_time=)
@@ -380,6 +475,7 @@ class Rnp
380
475
  armored: nil,
381
476
  compression: nil,
382
477
  cipher: nil,
478
+ aead: nil,
383
479
  hash: nil,
384
480
  creation_time: nil,
385
481
  expiration_time: nil)
@@ -389,6 +485,7 @@ class Rnp
389
485
  armored: armored,
390
486
  compression: compression,
391
487
  cipher: cipher,
488
+ aead: aead,
392
489
  hash: hash,
393
490
  creation_time: creation_time,
394
491
  expiration_time: expiration_time
@@ -406,6 +503,7 @@ class Rnp
406
503
  # @param armored (see Encrypt#armored=)
407
504
  # @param compression (see Encrypt#compression=)
408
505
  # @param cipher (see Encrypt#cipher=)
506
+ # @param aead (see Encrypt#aead=)
409
507
  # @param s2k_hash (see Encrypt#add_password)
410
508
  # @param s2k_iterations (see Encrypt#add_password)
411
509
  # @param s2k_cipher (see Encrypt#add_password)
@@ -414,6 +512,7 @@ class Rnp
414
512
  armored: nil,
415
513
  compression: nil,
416
514
  cipher: nil,
515
+ aead: nil,
417
516
  s2k_hash: nil,
418
517
  s2k_iterations: 0,
419
518
  s2k_cipher: nil)
@@ -422,7 +521,8 @@ class Rnp
422
521
  enc.options = {
423
522
  armored: armored,
424
523
  compression: compression,
425
- cipher: cipher
524
+ cipher: cipher,
525
+ aead: aead,
426
526
  }
427
527
  passwords = [passwords] if passwords.is_a?(String)
428
528
  passwords.each do |password|
@@ -447,6 +547,30 @@ class Rnp
447
547
  end
448
548
  end
449
549
 
550
+ # Start a {Generate} operation.
551
+ #
552
+ # @param type [String, Symbol] the key type to generate (RSA, DSA, etc)
553
+ # @return [Generate]
554
+ def start_generate(type:)
555
+ pptr = FFI::MemoryPointer.new(:pointer)
556
+ Rnp.call_ffi(:rnp_op_generate_create, pptr, @ptr, type.to_s)
557
+ pgen = pptr.read_pointer
558
+ Generate.new(pgen) unless pgen.null?
559
+ end
560
+
561
+ # Start a {Generate} operation.
562
+ #
563
+ # @param primary [Key] the primary key for which to generate a subkey
564
+ # @param type [String, Symbol] the key type to generate (RSA, DSA, etc)
565
+ # @return [Generate]
566
+ def start_generate_subkey(primary:, type:)
567
+ pptr = FFI::MemoryPointer.new(:pointer)
568
+ Rnp.call_ffi(:rnp_op_generate_subkey_create, pptr, @ptr, primary.ptr,
569
+ type.to_s)
570
+ pgen = pptr.read_pointer
571
+ Generate.new(pgen) unless pgen.null?
572
+ end
573
+
450
574
  # Create a {Sign} operation.
451
575
  #
452
576
  # @param input [Input] the input to read the data to be signed
@@ -499,6 +623,41 @@ class Rnp
499
623
  Encrypt.new(pencrypt) unless pencrypt.null?
500
624
  end
501
625
 
626
+ # Import keys
627
+ #
628
+ # @param input [Input] the input to read the (OpenPGP-format) keys from
629
+ # @param public_keys [Boolean] whether to load public keys
630
+ # @param secret_keys [Boolean] whether to load secret keys
631
+ # @return [Hash] information on the imported keys
632
+ def import_keys(input:, public_keys: true, secret_keys: true)
633
+ flags = 0
634
+ flags |= LibRnp::RNP_LOAD_SAVE_PUBLIC_KEYS if public_keys
635
+ flags |= LibRnp::RNP_LOAD_SAVE_SECRET_KEYS if secret_keys
636
+ pptr = FFI::MemoryPointer.new(:pointer)
637
+ Rnp.call_ffi(:rnp_import_keys, @ptr, input.ptr, flags, pptr)
638
+ begin
639
+ presults = pptr.read_pointer
640
+ JSON.parse(presults.read_string) unless pptr.null?
641
+ ensure
642
+ LibRnp.rnp_buffer_destroy(presults)
643
+ end
644
+ end
645
+
646
+ # Import signatures
647
+ #
648
+ # @param input [Input] the input to read the (OpenPGP-format) keys from
649
+ # @return [Hash] information on the imported keys
650
+ def import_signatures(input:)
651
+ pptr = FFI::MemoryPointer.new(:pointer)
652
+ Rnp.call_ffi(:rnp_import_signatures, @ptr, input.ptr, 0, pptr)
653
+ begin
654
+ presults = pptr.read_pointer
655
+ JSON.parse(presults.read_string) unless pptr.null?
656
+ ensure
657
+ LibRnp.rnp_buffer_destroy(presults)
658
+ end
659
+ end
660
+
502
661
  private
503
662
 
504
663
  KEY_PROVIDER = lambda do |provider, _rnp, _ctx, identifier_type, identifier, secret|
@@ -584,5 +743,12 @@ class Rnp
584
743
  flags |= LibRnp::RNP_LOAD_SAVE_SECRET_KEYS if secret_keys
585
744
  flags
586
745
  end
746
+
747
+ def unload_keys_flags(public_keys:, secret_keys:)
748
+ flags = 0
749
+ flags |= LibRnp::RNP_KEY_UNLOAD_PUBLIC if public_keys
750
+ flags |= LibRnp::RNP_KEY_UNLOAD_SECRET if secret_keys
751
+ flags
752
+ end
587
753
  end # class
588
754
 
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2020 Ribose Inc.
4
+
5
+ require 'ffi'
6
+
7
+ require 'rnp/error'
8
+ require 'rnp/ffi/librnp'
9
+ require 'rnp/utils'
10
+
11
+ class Rnp
12
+ # Class that represents a signature.
13
+ class Signature
14
+ # @api private
15
+ attr_reader :ptr
16
+
17
+ # @api private
18
+ def initialize(ptr)
19
+ raise Rnp::Error, 'NULL pointer' if ptr.null?
20
+ @ptr = FFI::AutoPointer.new(ptr, self.class.method(:destroy))
21
+ end
22
+
23
+ # @api private
24
+ def self.destroy(ptr)
25
+ LibRnp.rnp_signature_handle_destroy(ptr)
26
+ end
27
+
28
+ def inspect
29
+ Rnp.inspect_ptr(self)
30
+ end
31
+
32
+ # The type of the signing key (RSA, etc).
33
+ #
34
+ # @return [String]
35
+ def type
36
+ string_property(:rnp_signature_get_alg)
37
+ end
38
+
39
+ # The hash algorithm used in the signature.
40
+ #
41
+ # @return [String]
42
+ def hash
43
+ string_property(:rnp_signature_get_hash_alg)
44
+ end
45
+
46
+ # The signer's key id.
47
+ #
48
+ # @return [String]
49
+ def keyid
50
+ string_property(:rnp_signature_get_keyid)
51
+ end
52
+
53
+ # The time this signature was created at.
54
+ #
55
+ # @return [Time]
56
+ def creation_time
57
+ pcreation = FFI::MemoryPointer.new(:uint32)
58
+ Rnp.call_ffi(:rnp_signature_get_creation, @ptr, pcreation)
59
+ Time.at(pcreation.read(:uint32))
60
+ end
61
+
62
+ # The signer's key.
63
+ #
64
+ # @return [Key]
65
+ def signer
66
+ pptr = FFI::MemoryPointer.new(:pointer)
67
+ Rnp.call_ffi(:rnp_signature_get_signer, @ptr, pptr)
68
+ pkey = pptr.read_pointer
69
+ Key.new(pkey) unless pkey.null?
70
+ end
71
+
72
+ # JSON representation of this signature (as a Hash).
73
+ #
74
+ # @param mpi [Boolean] if true then MPIs will be included
75
+ # @param raw [Boolean] if true then raw data will be included
76
+ # @param grip [Boolean] if true then grips will be included
77
+ # @return [Hash]
78
+ def json(mpi: false, raw: false, grip: false)
79
+ flags = 0
80
+ flags |= LibRnp::RNP_JSON_DUMP_MPI if mpi
81
+ flags |= LibRnp::RNP_JSON_DUMP_RAW if raw
82
+ flags |= LibRnp::RNP_JSON_DUMP_GRIP if grip
83
+ pptr = FFI::MemoryPointer.new(:pointer)
84
+ Rnp.call_ffi(:rnp_signature_packet_to_json, @ptr, flags, pptr)
85
+ begin
86
+ pvalue = pptr.read_pointer
87
+ JSON.parse(pvalue.read_string) unless pvalue.null?
88
+ ensure
89
+ LibRnp.rnp_buffer_destroy(pvalue)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def string_property(func)
96
+ pptr = FFI::MemoryPointer.new(:pointer)
97
+ Rnp.call_ffi(func, @ptr, pptr)
98
+ begin
99
+ pvalue = pptr.read_pointer
100
+ pvalue.read_string unless pvalue.null?
101
+ ensure
102
+ LibRnp.rnp_buffer_destroy(pvalue)
103
+ end
104
+ end
105
+ end
106
+ end
data/lib/rnp/userid.rb ADDED
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2020 Ribose Inc.
4
+
5
+ require 'ffi'
6
+
7
+ require 'rnp/error'
8
+ require 'rnp/ffi/librnp'
9
+ require 'rnp/utils'
10
+ require 'rnp/signature'
11
+
12
+ class Rnp
13
+ # Class that represents a UserID
14
+ class UserID
15
+ # @api private
16
+ attr_reader :ptr
17
+
18
+ # @api private
19
+ def initialize(ptr, userid)
20
+ raise Rnp::Error, 'NULL pointer' if ptr.null?
21
+ @ptr = FFI::AutoPointer.new(ptr, self.class.method(:destroy))
22
+ @userid = userid
23
+ end
24
+
25
+ # @api private
26
+ def self.destroy(ptr)
27
+ LibRnp.rnp_uid_handle_destroy(ptr)
28
+ end
29
+
30
+ def inspect
31
+ Rnp.inspect_ptr(self)
32
+ end
33
+
34
+ def to_s
35
+ @userid
36
+ end
37
+
38
+ # Enumerate each {Signature} for this key.
39
+ #
40
+ # @return [self, Enumerator]
41
+ def each_signature(&block)
42
+ block or return enum_for(:signature_iterator)
43
+ signature_iterator(&block)
44
+ self
45
+ end
46
+
47
+ # Get a list of all {Signature}s for this key.
48
+ #
49
+ # @return [Array<Signature>]
50
+ def signatures
51
+ each_signature.to_a
52
+ end
53
+
54
+ # Check if this key is revoked.
55
+ #
56
+ # @return [Boolean]
57
+ def revoked?
58
+ presult = FFI::MemoryPointer.new(:bool)
59
+ Rnp.call_ffi(:rnp_uid_is_revoked, @ptr, presult)
60
+ presult.read(:bool)
61
+ end
62
+
63
+ private
64
+
65
+ def signature_iterator
66
+ pcount = FFI::MemoryPointer.new(:size_t)
67
+ Rnp.call_ffi(:rnp_uid_get_signature_count, @ptr, pcount)
68
+ count = pcount.read(:size_t)
69
+ (0...count).each do |i|
70
+ pptr = FFI::MemoryPointer.new(:pointer)
71
+ Rnp.call_ffi(:rnp_uid_get_signature_at, @ptr, i, pptr)
72
+ psig = pptr.read_pointer
73
+ yield Signature.new(psig) unless psig.null?
74
+ end
75
+ end
76
+ end
77
+ end
data/lib/rnp/version.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # (c) 2018 Ribose Inc.
3
+ # (c) 2018-2023 Ribose Inc.
4
4
 
5
5
  class Rnp
6
- VERSION = '1.0.3'
6
+ VERSION = "1.0.5"
7
7
  end # class
8
-
data/lib/rnp.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # (c) 2018 Ribose Inc.
3
+ # (c) 2018-2020 Ribose Inc.
4
4
 
5
5
  require 'rnp/error'
6
6
  require 'rnp/input'
@@ -10,5 +10,7 @@ require 'rnp/op/sign'
10
10
  require 'rnp/op/verify'
11
11
  require 'rnp/output'
12
12
  require 'rnp/rnp'
13
+ require 'rnp/signature'
14
+ require 'rnp/userid'
13
15
  require 'rnp/version'
14
16
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rnp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-21 00:00:00.000000000 Z
11
+ date: 2023-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -16,28 +16,34 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.5'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.5'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.14'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '3'
34
37
  type: :development
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
39
42
  - !ruby/object:Gem::Version
40
43
  version: '1.14'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '3'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: codecov
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -56,16 +62,22 @@ dependencies:
56
62
  name: rake
57
63
  requirement: !ruby/object:Gem::Requirement
58
64
  requirements:
59
- - - "~>"
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '10'
68
+ - - "<"
60
69
  - !ruby/object:Gem::Version
61
- version: '10.0'
70
+ version: '14'
62
71
  type: :development
63
72
  prerelease: false
64
73
  version_requirements: !ruby/object:Gem::Requirement
65
74
  requirements:
66
- - - "~>"
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '10'
78
+ - - "<"
67
79
  - !ruby/object:Gem::Version
68
- version: '10.0'
80
+ version: '14'
69
81
  - !ruby/object:Gem::Dependency
70
82
  name: redcarpet
71
83
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +112,14 @@ dependencies:
100
112
  requirements:
101
113
  - - "~>"
102
114
  - !ruby/object:Gem::Version
103
- version: 0.55.0
115
+ version: 0.75.0
104
116
  type: :development
105
117
  prerelease: false
106
118
  version_requirements: !ruby/object:Gem::Requirement
107
119
  requirements:
108
120
  - - "~>"
109
121
  - !ruby/object:Gem::Version
110
- version: 0.55.0
122
+ version: 0.75.0
111
123
  - !ruby/object:Gem::Dependency
112
124
  name: simplecov
113
125
  requirement: !ruby/object:Gem::Requirement
@@ -170,10 +182,13 @@ files:
170
182
  - lib/rnp/key.rb
171
183
  - lib/rnp/misc.rb
172
184
  - lib/rnp/op/encrypt.rb
185
+ - lib/rnp/op/generate.rb
173
186
  - lib/rnp/op/sign.rb
174
187
  - lib/rnp/op/verify.rb
175
188
  - lib/rnp/output.rb
176
189
  - lib/rnp/rnp.rb
190
+ - lib/rnp/signature.rb
191
+ - lib/rnp/userid.rb
177
192
  - lib/rnp/utils.rb
178
193
  - lib/rnp/version.rb
179
194
  homepage: https://www.ribose.com
@@ -189,15 +204,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
204
  requirements:
190
205
  - - ">="
191
206
  - !ruby/object:Gem::Version
192
- version: 2.3.0
207
+ version: 2.7.0
193
208
  required_rubygems_version: !ruby/object:Gem::Requirement
194
209
  requirements:
195
210
  - - ">="
196
211
  - !ruby/object:Gem::Version
197
212
  version: '0'
198
213
  requirements: []
199
- rubyforge_project:
200
- rubygems_version: 2.7.7
214
+ rubygems_version: 3.4.6
201
215
  signing_key:
202
216
  specification_version: 4
203
217
  summary: Ruby bindings for the rnp OpenPGP library